templates/lab_template-old.yml [1272:1616]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          http = urllib3.PoolManager()

          # Lambda handler function / main function
          def handler(event, context):
            print("[INFO]", "Invocation start")

            # init response
            props = event["ResourceProperties"]
            response_status = cfnresponse.FAILED
            response_data = {}

            # try/catch
            try:
              # lowercase names, set response as success
              response_data["DBClusterId"] = props["Cluster"].lower()
              response_data["DBClusterScalableTarget"] = "cluster:%s" % response_data["DBClusterId"]
              print("[INFO]", "ScalableTarget computed:", response_data["DBClusterScalableTarget"])

              # only send analytics if agreed
              if event["ResourceProperties"]["AgreeTracking"] == 'Yes':
                # try/catch
                try:
                  # track analytics
                  payload = {
                    'stack_uuid': props["StackUUID"] if ("StackUUID" in props) else None,
                    'stack_name': props["StackName"] if ("StackName" in props) else None,
                    'stack_region': props["StackRegion"] if ("StackRegion" in props) else None,
                    'deployed_cluster': props["DeployedCluster"] if ("DeployedCluster" in props) else None,
                    'deployed_ml': props["DeployedML"] if ("DeployedML" in props) else None,
                    'deployed_gdb': props["DeployedGDB"] if ("DeployedGDB" in props) else None,
                    'is_secondary': props["IsSecondary"] if ("IsSecondary" in props) else None,
                    'event_timestamp': datetime.datetime.utcnow().isoformat() + 'Z',
                    'event_scope': 'Stack',
                    'event_action': event["RequestType"] if "RequestType" in event else None,
                    'event_message': "Stack-level operation",
                    'ee_event_id': props["EEEventId"] if ("EEEventId" in props) else None,
                    'ee_team_id': props["EETeamId"] if ("EETeamId" in props) else None,
                    'ee_module_id': props["EEModuleId"] if ("EEModuleId" in props) else None,
                    'ee_module_version': props["EEModuleVersion"] if ("EEModuleVersion" in props) else None
                  }
                  r = http.request('POST', environ["ANALYTICSURI"], body=json.dumps(payload).encode('utf-8'), headers={'Content-Type': 'application/json'})
                  print("[INFO]", "Event tracking for UUID:", payload["stack_uuid"])
                except Exception as e:
                  # errors in tracker interaction should not prevent operation of the function in critical path
                  print("[ERROR]", e)
              else:
                print("[INFO]", "Opted out of analytics")

              # on stack creation
              if event["RequestType"] == 'Create':
                # wait until Cloud9 instance is available
                print("[INFO]", "Instance filter to use:", props['IDEEnvTagName'])
                result = ec2.describe_instances(Filters=[{'Name': 'tag:Name','Values': [props['IDEEnvTagName']]}])
                if ('Reservations' in result and len(result['Reservations']) > 0):
                  c9 = result['Reservations'][0]['Instances'][0]
                  print("[INFO]", "Found IDE instance", c9['InstanceId'])
                  while c9['State']['Name'] != 'running':
                    time.sleep(5)
                    c9 = ec2.describe_instances(InstanceIds=[c9['InstanceId']])['Reservations'][0]['Instances'][0]

                  # swap the profiles
                  prof = {
                    'Arn': props['IDEProfileArn'],
                    'Name': props['IDEProfileName']
                  }

                  # find iam instance profile association
                  result = ec2.describe_iam_instance_profile_associations(
                    Filters=[{'Name': 'instance-id', 'Values': [c9['InstanceId']]}]
                  )
                  if ('IamInstanceProfileAssociations' in result and len(result['IamInstanceProfileAssociations']) > 0):
                    assoc = result['IamInstanceProfileAssociations'][0]
                    print("[INFO]", "Replacing IAM profile association:", assoc['AssociationId'])
                    response = ec2.replace_iam_instance_profile_association(IamInstanceProfile=prof, AssociationId=assoc['AssociationId'])
                  else:
                    print("[INFO]", "Associating new IAM profile")
                    response = ec2.associate_iam_instance_profile(IamInstanceProfile=prof, InstanceId=c9['InstanceId'])

                  # attach workstation security group
                  attached_sgs = [sg['GroupId'] for sg in ide.Instance(c9['InstanceId']).security_groups]
                  attached_sgs.append(props["IDESecGroupId"])
                  response = ide.Instance(c9['InstanceId']).modify_attribute(Groups=attached_sgs)
                  print("[INFO]", "Attached SGs:", attached_sgs)

                  # wait for ssm registration
                  status = 'Offline'
                  tries = 0
                  while status != 'Online' and tries < 50:
                    response = ssm.describe_instance_information(Filters=[{'Key': 'InstanceIds', 'Values': [c9['InstanceId']]}])
                    if ('InstanceInformationList' in response and len(response['InstanceInformationList']) > 0):
                      status = response['InstanceInformationList'][0]['PingStatus']
                    tries += 1
                    time.sleep(10)
                  
                  # run ssm bootstrap
                  if status == 'Online':
                    response = ssm.send_command(InstanceIds=[c9['InstanceId']],DocumentName=props["IDEBoostrapDoc"])
                    print("[INFO]", "Started IDE bootstrap:", response)
                  else:
                    print("[ERROR]", "Timeout exceeded waiting for SSM registration")

              # success
              response_status = cfnresponse.SUCCESS

            except Exception as e:
              print("[ERROR]", e)

            # try/catch
            try:
              # send response to CloudFormation
              cfnresponse.send(event, context, response_status, response_data)
            except Exception as e:
              print("[ERROR]", e)
              response_status = cfnresponse.FAILED
            print("[INFO]", "Invocation end")
            return response_status

## Custom resource to assign cluster IAM role
  resLabSupport:
    Type: "Custom::resLabSupport"
    Properties:
      ServiceToken: !GetAtt funcLabSupport.Arn
      StackRegion: !Ref "AWS::Region"
      StackName: !Ref "AWS::StackName"
      StackUUID: !Select
        - 2
        - !Split
          - "/"
          - !Ref "AWS::StackId"
      AgreeTracking: !Ref agreeTracking
      DeployedCluster: !If [ condDeployDBCluster, "Yes", "No" ]
      DeployedML: !If [ condDeployMLResources, "Yes", "No" ]
      DeployedGDB: !If [ condDeployGDBResources, "Yes", "No" ]
      IsSecondary: !Ref isSecondary
      Cluster: !If [ condDeployDBCluster, !Ref dbCluster, "" ]
      EEEventId: !Ref EEEventId
      EETeamId: !Ref EETeamId
      EEModuleId: !Ref EEModuleId
      EEModuleVersion: !Ref EEModuleVersion
      IDEProfileArn: !GetAtt profileClientIDE.Arn
      IDEProfileName: !Ref profileClientIDE
      IDEEnvTagName: !If [ condEventEngine, !Sub "aws-cloud9-auroralab-client-ide-${c9ClientIDE}", !Sub "aws-cloud9-${AWS::StackName}-client-ide-${c9ClientIDE}" ]
      IDESecGroupId: !Ref clientSecGroup
      IDEBoostrapDoc: !Ref ssmDocClientBootstrap

## Create role for use with the lab account function
  roleLabAccount:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !If [ condEventEngine, !Sub "auroralab-account-${AWS::Region}", !Sub "${AWS::StackName}-account-${AWS::Region}" ]
      Description: "Role to permit the Lambda account functions to interact with relevant AWS APIs."
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - "sts:AssumeRole"
            Principal:
              Service:
                - "lambda.amazonaws.com"
      Policies:
        - PolicyName: inline-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: "arn:aws:logs:*:*:*"
              - Effect: Allow
                Action:
                  - "s3:ListBucket"
                  - "s3:GetObject"
                  - "s3:GetObjectVersion"
                  - "s3:DeleteObject"
                  - "s3:DeleteObjects"
                  - "s3:ListMultipartUploadParts"
                  - "s3:PutObject"
                  - "s3:ListObjects"
                  - "s3:ListObjectsV2"
                  - "s3:ListObjectVersions"
                Resource:
                  - !Sub "arn:aws:s3:::${bucketLabData}/*"
                  - !Sub "arn:aws:s3:::${bucketLabData}"
              - Effect: Allow
                Action:
                  - iam:CreateInstanceProfile
                  - iam:GetRole
                  - iam:PassRole
                  - iam:GetInstanceProfile
                  - iam:ListRoles
                  - iam:CreateRole
                  - iam:AttachRolePolicy
                  - iam:ListInstanceProfiles
                  - iam:AddRoleToInstanceProfile
                Resource: "*"
      Tags:
        - Key: Name
          Value: !If [ condEventEngine, !Sub "auroralab-account-${AWS::Region}", !Sub "${AWS::StackName}-account-${AWS::Region}" ]

## Create Lambda function to implement conditional initialization and cleanup of accounts
  funcLabAccount:
    Type: "AWS::Lambda::Function"
    DependsOn: [ bucketLabData ]
    Properties:
      FunctionName: !If [ condEventEngine, "auroralab-account", !Sub "${AWS::StackName}-account" ]
      Description: "Custom Resource to provide account initialization and cleanup for the Aurora MySQL labs."
      Handler: "index.handler"
      Role: !GetAtt roleLabAccount.Arn
      Runtime: "python3.8"
      Timeout: 600
      Tags:
        - Key: Name
          Value: !If [ condEventEngine, "auroralab-account", !Sub "${AWS::StackName}-account" ]
      Environment:
        Variables:
          REGION: !Ref "AWS::Region"
      Code:
        ZipFile: |
          # Dependencies
          from os import environ
          import cfnresponse
          import boto3
          import urllib3
          import json
          import datetime
          import time

          print("[INFO]", "Initialize function")
          session = boto3.session.Session(region_name=environ["REGION"])
          s3 = session.resource('s3')
          iam = session.client('iam')

          # Lambda handler function / main function
          def handler(event, context):
            print("[INFO]", "Invocation start")

            # init response
            props = event["ResourceProperties"]
            response_status = cfnresponse.FAILED
            response_data = {}

            # try/catch
            try:
              # on stack creation
              if event["RequestType"] == 'Create':
                # does the Cloud9 standard role exist?
                missing = False
                try:
                  response = iam.get_role(RoleName='AWSCloud9SSMAccessRole')
                  print("[INFO]", "IAM role exists:", "AWSCloud9SSMAccessRole")
                except iam.exceptions.NoSuchEntityException:
                  missing = True
                except Exception as e:
                  print("[ERROR]", e)

                # create it if it doesn't exist
                if missing:
                  response = iam.create_role(
                    Path='/service-role/',
                    RoleName='AWSCloud9SSMAccessRole',
                    AssumeRolePolicyDocument='{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Service": ["ec2.amazonaws.com","cloud9.amazonaws.com"]},"Action": "sts:AssumeRole"}]}'
                  )
                  response = iam.attach_role_policy(
                    RoleName='AWSCloud9SSMAccessRole',
                    PolicyArn='arn:aws:iam::aws:policy/AWSCloud9SSMInstanceProfile'
                  )
                  response = iam.attach_role_policy(
                      RoleName='AWSCloud9SSMAccessRole',
                      PolicyArn='arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'
                  )
                  print("[INFO]", "Created IAM role:", "AWSCloud9SSMAccessRole")

                # does the Cloud9 standard instance profile exist?
                missing = False
                try:
                  response = iam.get_instance_profile(InstanceProfileName='AWSCloud9SSMInstanceProfile')
                  print("[INFO]", "IAM instance profile exists:", "AWSCloud9SSMInstanceProfile")
                except iam.exceptions.NoSuchEntityException:
                  missing = True
                except Exception as e:
                  print("[ERROR]", e)

                # create it if it doesn't exit
                if missing:
                  response = iam.create_instance_profile(
                    InstanceProfileName='AWSCloud9SSMInstanceProfile',
                    Path='/cloud9/'
                  )
                  response = iam.add_role_to_instance_profile(
                    InstanceProfileName='AWSCloud9SSMInstanceProfile',
                    RoleName='AWSCloud9SSMAccessRole'
                  )
                  print("[INFO]", "Created IAM instance profile:", "AWSCloud9SSMInstanceProfile")

              # on stack deletion
              if event["RequestType"] == 'Delete':
                # delete all objects out of the bucket
                bucket = s3.Bucket(props["DataBucket"])
                bucket.objects.delete()
                print("[INFO]", "Bucket cleaned up for:", props["DataBucket"])

              # success
              response_status = cfnresponse.SUCCESS

            except Exception as e:
              print("[ERROR]", e)

            # try/catch
            try:
              # send response to CloudFormation
              cfnresponse.send(event, context, response_status, response_data)
            except Exception as e:
              print("[ERROR]", e)
              response_status = cfnresponse.FAILED
            print("[INFO]", "Invocation end")
            return response_status

## Custom resource to implement initialization and cleanup of account
  resLabAccount:
    Type: "Custom::resLabAccount"
    Properties:
      ServiceToken: !GetAtt funcLabAccount.Arn
      DataBucket: !Ref bucketLabData
      StackRegion: !Ref "AWS::Region"
      StackName: !Ref "AWS::StackName"
      StackUUID: !Select
        - 2
        - !Split
          - "/"
          - !Ref "AWS::StackId"
      EEModuleId: !Ref EEModuleId
      EEModuleVersion: !Ref EEModuleVersion

## Wait condition to signal bootstrap completion
  bootstrapWaitHandle:
    Type: "AWS::CloudFormation::WaitConditionHandle"
  bootstrapWaitCondition:
    Type: "AWS::CloudFormation::WaitCondition"
    DependsOn: [ resLabSupport ]
    Properties: 
      Count: 1
      Handle: !Ref bootstrapWaitHandle
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



templates/lab_template.yml [1369:1713]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          http = urllib3.PoolManager()

          # Lambda handler function / main function
          def handler(event, context):
            print("[INFO]", "Invocation start")

            # init response
            props = event["ResourceProperties"]
            response_status = cfnresponse.FAILED
            response_data = {}

            # try/catch
            try:
              # lowercase names, set response as success
              response_data["DBClusterId"] = props["Cluster"].lower()
              response_data["DBClusterScalableTarget"] = "cluster:%s" % response_data["DBClusterId"]
              print("[INFO]", "ScalableTarget computed:", response_data["DBClusterScalableTarget"])

              # only send analytics if agreed
              if event["ResourceProperties"]["AgreeTracking"] == 'Yes':
                # try/catch
                try:
                  # track analytics
                  payload = {
                    'stack_uuid': props["StackUUID"] if ("StackUUID" in props) else None,
                    'stack_name': props["StackName"] if ("StackName" in props) else None,
                    'stack_region': props["StackRegion"] if ("StackRegion" in props) else None,
                    'deployed_cluster': props["DeployedCluster"] if ("DeployedCluster" in props) else None,
                    'deployed_ml': props["DeployedML"] if ("DeployedML" in props) else None,
                    'deployed_gdb': props["DeployedGDB"] if ("DeployedGDB" in props) else None,
                    'is_secondary': props["IsSecondary"] if ("IsSecondary" in props) else None,
                    'event_timestamp': datetime.datetime.utcnow().isoformat() + 'Z',
                    'event_scope': 'Stack',
                    'event_action': event["RequestType"] if "RequestType" in event else None,
                    'event_message': "Stack-level operation",
                    'ee_event_id': props["EEEventId"] if ("EEEventId" in props) else None,
                    'ee_team_id': props["EETeamId"] if ("EETeamId" in props) else None,
                    'ee_module_id': props["EEModuleId"] if ("EEModuleId" in props) else None,
                    'ee_module_version': props["EEModuleVersion"] if ("EEModuleVersion" in props) else None
                  }
                  r = http.request('POST', environ["ANALYTICSURI"], body=json.dumps(payload).encode('utf-8'), headers={'Content-Type': 'application/json'})
                  print("[INFO]", "Event tracking for UUID:", payload["stack_uuid"])
                except Exception as e:
                  # errors in tracker interaction should not prevent operation of the function in critical path
                  print("[ERROR]", e)
              else:
                print("[INFO]", "Opted out of analytics")

              # on stack creation
              if event["RequestType"] == 'Create':
                # wait until Cloud9 instance is available
                print("[INFO]", "Instance filter to use:", props['IDEEnvTagName'])
                result = ec2.describe_instances(Filters=[{'Name': 'tag:Name','Values': [props['IDEEnvTagName']]}])
                if ('Reservations' in result and len(result['Reservations']) > 0):
                  c9 = result['Reservations'][0]['Instances'][0]
                  print("[INFO]", "Found IDE instance", c9['InstanceId'])
                  while c9['State']['Name'] != 'running':
                    time.sleep(5)
                    c9 = ec2.describe_instances(InstanceIds=[c9['InstanceId']])['Reservations'][0]['Instances'][0]

                  # swap the profiles
                  prof = {
                    'Arn': props['IDEProfileArn'],
                    'Name': props['IDEProfileName']
                  }

                  # find iam instance profile association
                  result = ec2.describe_iam_instance_profile_associations(
                    Filters=[{'Name': 'instance-id', 'Values': [c9['InstanceId']]}]
                  )
                  if ('IamInstanceProfileAssociations' in result and len(result['IamInstanceProfileAssociations']) > 0):
                    assoc = result['IamInstanceProfileAssociations'][0]
                    print("[INFO]", "Replacing IAM profile association:", assoc['AssociationId'])
                    response = ec2.replace_iam_instance_profile_association(IamInstanceProfile=prof, AssociationId=assoc['AssociationId'])
                  else:
                    print("[INFO]", "Associating new IAM profile")
                    response = ec2.associate_iam_instance_profile(IamInstanceProfile=prof, InstanceId=c9['InstanceId'])

                  # attach workstation security group
                  attached_sgs = [sg['GroupId'] for sg in ide.Instance(c9['InstanceId']).security_groups]
                  attached_sgs.append(props["IDESecGroupId"])
                  response = ide.Instance(c9['InstanceId']).modify_attribute(Groups=attached_sgs)
                  print("[INFO]", "Attached SGs:", attached_sgs)

                  # wait for ssm registration
                  status = 'Offline'
                  tries = 0
                  while status != 'Online' and tries < 50:
                    response = ssm.describe_instance_information(Filters=[{'Key': 'InstanceIds', 'Values': [c9['InstanceId']]}])
                    if ('InstanceInformationList' in response and len(response['InstanceInformationList']) > 0):
                      status = response['InstanceInformationList'][0]['PingStatus']
                    tries += 1
                    time.sleep(10)
                  
                  # run ssm bootstrap
                  if status == 'Online':
                    response = ssm.send_command(InstanceIds=[c9['InstanceId']],DocumentName=props["IDEBoostrapDoc"])
                    print("[INFO]", "Started IDE bootstrap:", response)
                  else:
                    print("[ERROR]", "Timeout exceeded waiting for SSM registration")
                
              # success
              response_status = cfnresponse.SUCCESS
              
            except Exception as e:
              print("[ERROR]", e)

            # try/catch
            try:
              # send response to CloudFormation
              cfnresponse.send(event, context, response_status, response_data)
            except Exception as e:
              print("[ERROR]", e)
              response_status = cfnresponse.FAILED
            print("[INFO]", "Invocation end")
            return response_status

## Custom resource to assign cluster IAM role
  resLabSupport:
    Type: "Custom::resLabSupport"
    Properties:
      ServiceToken: !GetAtt funcLabSupport.Arn
      StackRegion: !Ref "AWS::Region"
      StackName: !Ref "AWS::StackName"
      StackUUID: !Select
        - 2
        - !Split
          - "/"
          - !Ref "AWS::StackId"
      AgreeTracking: !Ref agreeTracking
      DeployedCluster: !If [ condDeployDBCluster, "Yes", "No" ]
      DeployedML: !If [ condDeployMLResources, "Yes", "No" ]
      DeployedGDB: !If [ condDeployGDBResources, "Yes", "No" ]
      IsSecondary: !Ref isSecondary
      Cluster: !If [ condDeployDBCluster, !Ref dbCluster, "" ]
      EEEventId: !Ref EEEventId
      EETeamId: !Ref EETeamId
      EEModuleId: !Ref EEModuleId
      EEModuleVersion: !Ref EEModuleVersion
      IDEProfileArn: !GetAtt profileClientIDE.Arn
      IDEProfileName: !Ref profileClientIDE
      IDEEnvTagName: !If [ condEventEngine, !Sub "aws-cloud9-auroralab-client-ide-${c9ClientIDE}", !Sub "aws-cloud9-${AWS::StackName}-client-ide-${c9ClientIDE}" ]
      IDESecGroupId: !Ref clientSecGroup
      IDEBoostrapDoc: !Ref ssmDocClientBootstrap

## Create role for use with the lab account function
  roleLabAccount:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !If [ condEventEngine, !Sub "auroralab-account-${AWS::Region}", !Sub "${AWS::StackName}-account-${AWS::Region}" ]
      Description: "Role to permit the Lambda account functions to interact with relevant AWS APIs."
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - "sts:AssumeRole"
            Principal:
              Service:
                - "lambda.amazonaws.com"
      Policies:
        - PolicyName: inline-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: "arn:aws:logs:*:*:*"
              - Effect: Allow
                Action:
                  - "s3:ListBucket"
                  - "s3:GetObject"
                  - "s3:GetObjectVersion"
                  - "s3:DeleteObject"
                  - "s3:DeleteObjects"
                  - "s3:ListMultipartUploadParts"
                  - "s3:PutObject"
                  - "s3:ListObjects"
                  - "s3:ListObjectsV2"
                  - "s3:ListObjectVersions"
                Resource:
                  - !Sub "arn:aws:s3:::${bucketLabData}/*"
                  - !Sub "arn:aws:s3:::${bucketLabData}"
              - Effect: Allow
                Action:
                  - iam:CreateInstanceProfile
                  - iam:GetRole
                  - iam:PassRole
                  - iam:GetInstanceProfile
                  - iam:ListRoles
                  - iam:CreateRole
                  - iam:AttachRolePolicy
                  - iam:ListInstanceProfiles
                  - iam:AddRoleToInstanceProfile
                Resource: "*"
      Tags:
        - Key: Name
          Value: !If [ condEventEngine, !Sub "auroralab-account-${AWS::Region}", !Sub "${AWS::StackName}-account-${AWS::Region}" ]

## Create Lambda function to implement conditional initialization and cleanup of accounts
  funcLabAccount:
    Type: "AWS::Lambda::Function"
    DependsOn: [ bucketLabData ]
    Properties:
      FunctionName: !If [ condEventEngine, "auroralab-account", !Sub "${AWS::StackName}-account" ]
      Description: "Custom Resource to provide account initialization and cleanup for the Aurora MySQL labs."
      Handler: "index.handler"
      Role: !GetAtt roleLabAccount.Arn
      Runtime: "python3.8"
      Timeout: 600
      Tags:
        - Key: Name
          Value: !If [ condEventEngine, "auroralab-account", !Sub "${AWS::StackName}-account" ]
      Environment:
        Variables:
          REGION: !Ref "AWS::Region"
      Code:
        ZipFile: |
          # Dependencies
          from os import environ
          import cfnresponse
          import boto3
          import urllib3
          import json
          import datetime
          import time

          print("[INFO]", "Initialize function")
          session = boto3.session.Session(region_name=environ["REGION"])
          s3 = session.resource('s3')
          iam = session.client('iam')

          # Lambda handler function / main function
          def handler(event, context):
            print("[INFO]", "Invocation start")

            # init response
            props = event["ResourceProperties"]
            response_status = cfnresponse.FAILED
            response_data = {}

            # try/catch
            try:
              # on stack creation
              if event["RequestType"] == 'Create':
                # does the Cloud9 standard role exist?
                missing = False
                try:
                  response = iam.get_role(RoleName='AWSCloud9SSMAccessRole')
                  print("[INFO]", "IAM role exists:", "AWSCloud9SSMAccessRole")
                except iam.exceptions.NoSuchEntityException:
                  missing = True
                except Exception as e:
                  print("[ERROR]", e)

                # create it if it doesn't exist
                if missing:
                  response = iam.create_role(
                    Path='/service-role/',
                    RoleName='AWSCloud9SSMAccessRole',
                    AssumeRolePolicyDocument='{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Service": ["ec2.amazonaws.com","cloud9.amazonaws.com"]},"Action": "sts:AssumeRole"}]}'
                  )
                  response = iam.attach_role_policy(
                    RoleName='AWSCloud9SSMAccessRole',
                    PolicyArn='arn:aws:iam::aws:policy/AWSCloud9SSMInstanceProfile'
                  )
                  response = iam.attach_role_policy(
                      RoleName='AWSCloud9SSMAccessRole',
                      PolicyArn='arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'
                  )
                  print("[INFO]", "Created IAM role:", "AWSCloud9SSMAccessRole")

                # does the Cloud9 standard instance profile exist?
                missing = False
                try:
                  response = iam.get_instance_profile(InstanceProfileName='AWSCloud9SSMInstanceProfile')
                  print("[INFO]", "IAM instance profile exists:", "AWSCloud9SSMInstanceProfile")
                except iam.exceptions.NoSuchEntityException:
                  missing = True
                except Exception as e:
                  print("[ERROR]", e)

                # create it if it doesn't exit
                if missing:
                  response = iam.create_instance_profile(
                    InstanceProfileName='AWSCloud9SSMInstanceProfile',
                    Path='/cloud9/'
                  )
                  response = iam.add_role_to_instance_profile(
                    InstanceProfileName='AWSCloud9SSMInstanceProfile',
                    RoleName='AWSCloud9SSMAccessRole'
                  )
                  print("[INFO]", "Created IAM instance profile:", "AWSCloud9SSMInstanceProfile")

              # on stack deletion
              if event["RequestType"] == 'Delete':
                # delete all objects out of the bucket
                bucket = s3.Bucket(props["DataBucket"])
                bucket.objects.delete()
                print("[INFO]", "Bucket cleaned up for:", props["DataBucket"])

              # success
              response_status = cfnresponse.SUCCESS

            except Exception as e:
              print("[ERROR]", e)

            # try/catch
            try:
              # send response to CloudFormation
              cfnresponse.send(event, context, response_status, response_data)
            except Exception as e:
              print("[ERROR]", e)
              response_status = cfnresponse.FAILED
            print("[INFO]", "Invocation end")
            return response_status

## Custom resource to implement initialization and cleanup of account
  resLabAccount:
    Type: "Custom::resLabAccount"
    Properties:
      ServiceToken: !GetAtt funcLabAccount.Arn
      DataBucket: !Ref bucketLabData
      StackRegion: !Ref "AWS::Region"
      StackName: !Ref "AWS::StackName"
      StackUUID: !Select
        - 2
        - !Split
          - "/"
          - !Ref "AWS::StackId"
      EEModuleId: !Ref EEModuleId
      EEModuleVersion: !Ref EEModuleVersion

## Wait condition to signal bootstrap completion
  bootstrapWaitHandle:
    Type: "AWS::CloudFormation::WaitConditionHandle"
  bootstrapWaitCondition:
    Type: "AWS::CloudFormation::WaitCondition"
    DependsOn: [ resLabSupport ]
    Properties: 
      Count: 1
      Handle: !Ref bootstrapWaitHandle
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



