#########################################################################################
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.                    #
# SPDX-License-Identifier: MIT-0                                                        #
#                                                                                       #
# Permission is hereby granted, free of charge, to any person obtaining a copy of this  #
# software and associated documentation files (the "Software"), to deal in the Software #
# without restriction, including without limitation the rights to use, copy, modify,    #
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to    #
# permit persons to whom the Software is furnished to do so.                            #
#                                                                                       #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,   #
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A         #
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT    #
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION     #
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE        #
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                                #
#########################################################################################

AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation template Cloud9 setup to help with MGN pre-reqs setup
Parameters:
  Cloud9InstanceType:
    Description: Example Cloud9 instance type
    Type: String
    Default: t3.small
    AllowedValues:
      - t2.micro
      - t3.micro
      - t3.small
      - t3.medium
    ConstraintDescription: Must be a valid Cloud9 instance type
  Cloud9InstanceVolumeSize: 
    Type: Number
    Description: The Size in GB of the Cloud9 Instance Volume. 
    Default: 15
  S3BucketName:
    Type: String
    Description: The S3 bucket where packaged artifacts should be uploaded
  MGNCodeZipUrl:
    Type: String
    Description: URL for MGN Code base zip

Resources:
################## PERMISSIONS AND ROLES #################
  MGNC9Role:
    Type: AWS::IAM::Role
    Properties:
      Tags:
        - Key: Environment
          Value: AWS Example
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ec2.amazonaws.com
            - ssm.amazonaws.com
          Action:
          - sts:AssumeRole
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/AdministratorAccess
      Path: "/"

  MGNC9LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName:
          Fn::Join:
          - ''
          - - C9LambdaPolicy-
            - Ref: AWS::Region
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - logs:CreateLogGroup
            - logs:CreateLogStream
            - logs:PutLogEvents
            Resource: arn:aws:logs:*:*:*
          - Effect: Allow
            Action:
            - cloudformation:DescribeStacks
            - cloudformation:DescribeStackEvents
            - cloudformation:DescribeStackResource
            - cloudformation:DescribeStackResources
            - ec2:DescribeInstances
            - ec2:AssociateIamInstanceProfile
            - ec2:ModifyInstanceAttribute
            - ec2:ReplaceIamInstanceProfileAssociation
            - iam:ListInstanceProfiles
            - ssm:ListCommandInvocations
            - s3:List*
            Resource: "*"
          - Effect: Allow
            Action: s3:GetObject
            Resource: !Sub arn:aws:s3:::${S3BucketName}/central_account_monitoring_resources_packaged.yaml
          - Effect: Allow
            Action: iam:PassRole
            Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/${AWS::StackName}-MGNC9Role-*

################## LAMBDA BOOTSTRAP FUNCTION ################

  MGNC9BootstrapCustomResource:
    Type: Custom::MGNC9BootstrapCustomResource
    Properties:
      Tags:
        - Key: SSMBootstrap
          Value: Active
      ServiceToken:
        Fn::GetAtt:
        - MGNC9BootstrapInstanceLambdaFunction
        - Arn
      REGION:
        Ref: AWS::Region
      StackName:
        Ref: AWS::StackName
      EnvironmentId:
        Ref: MGNC9Instance
      LabIdeInstanceProfileName:
        Ref: MGNC9InstanceProfile
      LabIdeInstanceProfileArn:
        Fn::GetAtt:
        - MGNC9InstanceProfile
        - Arn

  MGNC9BootstrapInstanceLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: MGNC9BootstrapInstanceLambdaFunction
      Tags:
        - Key: Environment
          Value: AWS Example
      Handler: index.lambda_handler
      Role:
        Fn::GetAtt:
        - MGNC9LambdaExecutionRole
        - Arn
      Runtime: python3.7
      MemorySize: 256
      Timeout: '600'
      Code:
        ZipFile: |
          from __future__ import print_function
          import boto3
          import json
          import os
          import time
          import traceback
          import cfnresponse

          def lambda_handler(event, context):
              print('Event Info')
              print(json.dumps(event))
              responseData = {"status": 'Success'}
              # cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID')

              # logger.info('context: {}'.format(context))
              responseData = {}
              ec2 = boto3.client('ec2')
              if event['RequestType'] == 'Create':
                  try:
                      # Get the InstanceId of the Cloud9 IDE
                      instance_details = ec2.describe_instances(Filters=[{'Name': 'tag:SSMBootstrap', 'Values': ["Active"]},
                                                                  {'Name': 'instance-state-name', 'Values': ['running']}])
                      if instance_details:
                          instance = instance_details['Reservations'][0]['Instances'][0]
                          # Create the IamInstanceProfile request object
                          iam_instance_profile = {
                              'Arn': event['ResourceProperties']['LabIdeInstanceProfileArn'],
                              'Name': event['ResourceProperties']['LabIdeInstanceProfileName']
                          }
                          instance_state = instance['State']['Name']
                          print("instance state: " + instance_state)
                          while instance_state != 'running':
                              time.sleep(5)
                              instance_state = ec2.describe_instances(InstanceIds=[instance['InstanceId']])

                          # attach instance profile
                          response = ec2.associate_iam_instance_profile(IamInstanceProfile=iam_instance_profile, InstanceId=instance['InstanceId'])
                          responseData = {'Success': 'Attached instance profile to instance: '+instance['InstanceId']}
                          cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID')
                      else:
                          print("No Cloud9 instances can be found")
                  except Exception as e:
                      print("Error associating instance profile to Cloud9 instance")
                      print(traceback.format_exc())
                      responseData = {'Error': str(e)}
                      cfnresponse.send(event, context, cfnresponse.FAILED, responseData, 'CustomResourcePhysicalID')
              else:
                responseData = {'Success': 'Delete or Update operations not supported so skipping'}
                cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID')
                    
################## SSM BOOTSRAP HANDLER ###############
  MGNC9OutputBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties:
      BucketEncryption: 
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

  MGNC9SSMDocument: 
    Type: AWS::SSM::Document
    Properties: 
      Tags:
        - Key: Environment
          Value: AWS Example
      DocumentFormat: YAML
      DocumentType: Command
      Content: 
        schemaVersion: '2.2'
        description: Bootstrap Cloud9 Instance
        mainSteps:
        - action: aws:runShellScript
          name: C9bootstrap
          inputs:
            runCommand:
            - "#!/bin/bash"
            - date
            - echo LANG=en_US.utf-8 >> /etc/environment
            - echo LC_ALL=en_US.UTF-8 >> /etc/environment
            - . /home/ec2-user/.bashrc
            - yum -y remove aws-cli; yum -y install sqlite telnet jq strace tree gcc glibc-static python3 python3-pip gettext bash-completion
            - echo '=== CONFIGURE default python version ==='
            - PATH=$PATH:/usr/bin
            - alternatives --set python /usr/bin/python3.7
            - echo '=== INSTALL and CONFIGURE default software components ==='
            - sudo -H -u ec2-user bash -c "pip install --user -U boto boto3 botocore awscli aws-sam-cli"
            - echo '=== Resizing the Instance volume'
            - !Sub SIZE=${Cloud9InstanceVolumeSize}
            - !Sub REGION=${AWS::Region}
            - |
              INSTANCEID=$(curl http://169.254.169.254/latest/meta-data/instance-id)
              VOLUMEID=$(aws ec2 describe-instances \
                --instance-id $INSTANCEID \
                --query "Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId" \
                --output text --region $REGION)
              aws ec2 modify-volume --volume-id $VOLUMEID --size $SIZE --region $REGION
              while [ \
                "$(aws ec2 describe-volumes-modifications \
                  --volume-id $VOLUMEID \
                  --filters Name=modification-state,Values="optimizing","completed" \
                  --query "length(VolumesModifications)"\
                  --output text --region $REGION)" != "1" ]; do
              sleep 1
              done
              if [ $(readlink -f /dev/xvda) = "/dev/xvda" ]
              then
                sudo growpart /dev/xvda 1
                STR=$(cat /etc/os-release)
                SUB="VERSION_ID=\"2\""
                if [[ "$STR" == *"$SUB"* ]]
                then
                  sudo xfs_growfs -d /
                else
                  sudo resize2fs /dev/xvda1
                fi
              else
                sudo growpart /dev/nvme0n1 1
                STR=$(cat /etc/os-release)
                SUB="VERSION_ID=\"2\""
                if [[ "$STR" == *"$SUB"* ]]
                then
                  sudo xfs_growfs -d /
                else
                  sudo resize2fs /dev/nvme0n1p1
                fi
              fi
            - echo '=== CONFIGURE awscli and setting ENVIRONMENT VARS ==='
            - echo "complete -C '/usr/local/bin/aws_completer' aws" >> /home/ec2-user/.bashrc
            - mkdir /home/ec2-user/.aws
            - echo '[default]' > /home/ec2-user/.aws/config
            - echo 'output = json' >> /home/ec2-user/.aws/config
            - chmod 600 /home/ec2-user/.aws/config && chmod 600 /home/ec2-user/.aws/credentials
            - echo 'PATH=$PATH:/usr/local/bin' >> /home/ec2-user/.bashrc
            - echo 'export PATH' >> /home/ec2-user/.bashrc
            - echo '=== CLEANING /home/ec2-user ==='
            - for f in cloud9; do rm -rf /home/ec2-user/$f; done
            - chown -R ec2-user:ec2-user /home/ec2-user/
            - echo '=== PREPARE REBOOT in 1 minute with at ==='
            - FILE=$(mktemp) && echo $FILE && echo '#!/bin/bash' > $FILE && echo 'reboot -f --verbose' >> $FILE && at now + 1 minute -f $FILE
            - !Sub echo "Running Command == wget -O main.zip ${MGNCodeZipUrl}"
            - !Sub wget -O main.zip "${MGNCodeZipUrl}"
            - unzip -u main.zip
            - cd aws-application-migration-service-monitoring-main
            - !Sub ./cfn_package.sh ${S3BucketName}
            - echo "Bootstrap completed with return code $?"
  
  MGNCloud9BootstrapAssociation: 
    Type: AWS::SSM::Association
    DependsOn: 
      - MGNC9BootstrapCustomResource
    Properties: 
      Name: !Ref MGNC9SSMDocument
      OutputLocation: 
        S3Location:
          OutputS3BucketName: !Ref MGNC9OutputBucket
          OutputS3KeyPrefix: bootstrapoutput
      Targets:
        - Key: tag:SSMBootstrap
          Values:
          - Active

################## INSTANCE #####################
  MGNC9InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/"
      Roles:
      - Ref: MGNC9Role

  MGNC9Instance:
    Type: AWS::Cloud9::EnvironmentEC2
    Properties:
      Description: "Cloud 9 environment to install MGN pre-reqs"
      AutomaticStopTimeMinutes: 1800
      InstanceType:
        Ref: Cloud9InstanceType
      Name:
        Ref: AWS::StackName
      Tags: 
        - 
          Key: SSMBootstrap
          Value: Active
        - 
          Key: Environment
          Value: MGN-Cloud9

################## LAMBDA SSM DOCUMENT RUN COMMAND FUNCTION ################

  MGNCustomResourceSSMDocumentWait:
    Type: Custom::MGNCustomResourceSSMDocumentWait
    Properties:
      Tags:
        - Key: RUNCOMMANDWAIT
          Value: Active
      ServiceToken:
        Fn::GetAtt:
        - MGNSSMWaitLambdaFunction
        - Arn
      Region:
        Ref: AWS::Region
      StackName:
        Ref: AWS::StackName
      EnvironmentId:
        Ref: MGNC9Instance
      S3BucketName:
        Ref: S3BucketName
      S3ObjectName: 'central_account_monitoring_resources_packaged.yaml'

  MGNSSMWaitLambdaFunction:
    Type: AWS::Lambda::Function
    DependsOn: MGNC9SSMDocument
    Properties:
      FunctionName: MGNSSMWaitLambdaFunction
      Tags:
        - Key: Environment
          Value: AWS Example
      Handler: index.lambda_handler
      Role:
        Fn::GetAtt:
        - MGNC9LambdaExecutionRole
        - Arn
      Runtime: python3.7
      MemorySize: 256
      Timeout: '900'
      Code:
        ZipFile: |
          from __future__ import print_function
          import boto3
          import json
          import os
          import time
          import traceback
          import cfnresponse
          import sys

          def fetch_object(bucket_name, object_name, region, iter=180):
              try:
                  s3 = boto3.client('s3', region_name=region)
                  while True:
                      try:
                          response = s3.get_object(
                              Bucket=bucket_name,
                              Key=object_name
                          )
                          return response
                      except s3.exceptions.NoSuchKey:
                          print("Waiting for packaged CloudFormation template called: " + object_name + " in the bucket: " + bucket_name)
                          time.sleep(5)
                          iter -= 1
                          if iter == 0:
                              raise TimeoutError("The Systems Manager Run Command Operation To Package CFN via Cloud9 Timed Out, Exiting...")
                          continue
              except s3.exceptions.InvalidObjectState as error:
                raise error
              except Exception as error:
                raise error

          def lambda_handler(event, context):
              print('Event Info')
              print(json.dumps(event))
              responseData = {"status": 'Success'}
              # cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID')

              # logger.info('context: {}'.format(context))
              region = event['ResourceProperties']['Region']
              responseData = {}
              if event['RequestType'] == 'Create': 
                  try: 
                      bucket_name = event['ResourceProperties']['S3BucketName']
                      object_name = event['ResourceProperties']['S3ObjectName']
                      response = fetch_object(bucket_name, object_name, region)
                      print("The object has been created in the bucket: " + event['ResourceProperties']['S3BucketName'] + " in " + event['ResourceProperties']['Region'])
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID')
                  except Exception as e:
                      print("Error running the SSM Document")
                      print(traceback.format_exc())
                      responseData = {'Error': str(e)}
                      cfnresponse.send(event, context, cfnresponse.FAILED, responseData, 'CustomResourcePhysicalID')
              else:
                  responseData = {'Success': 'Delete or Update operations not supported so skipping'}
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID')

Outputs: 
  Cloud9IDE:
    Value:
      Fn::Join:
      - ''
      - - https://
        - Ref: AWS::Region
        - ".console.aws.amazon.com/cloud9/ide/"
        - Ref: MGNC9Instance
        - "?region="
        - Ref: AWS::Region