--- AWSTemplateFormatVersion: '2010-09-09' Description: AWS CloudFormation template for dynamic Cloud 9 setups. Creates a Cloud9 bootstraps the instance. Parameters: ExampleC9InstanceType: 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 ExampleC9EnvType: Description: Environment type. Default: self Type: String AllowedValues: - self - 3rdParty ConstraintDescription: must specify self or 3rdParty. ExampleOwnerArn: Type: String Description: The Arn of the Cloud9 Owner to be set if 3rdParty deployment. Default: "" ExampleC9InstanceVolumeSize: Type: Number Description: The Size in GB of the Cloud9 Instance Volume. Default: 15 Conditions: Create3rdPartyResources: !Equals [ !Ref ExampleC9EnvType, 3rdParty ] Resources: ################## PERMISSIONS AND ROLES ################# ExampleC9Role: 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: "/" ExampleC9LambdaExecutionRole: 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: - '' - - ExampleC9LambdaPolicy- - 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 - iam:PassRole Resource: "*" ################## LAMBDA BOOTSTRAP FUNCTION ################ ExampleC9BootstrapInstanceLambda: Description: Bootstrap Cloud9 instance Type: Custom::ExampleC9BootstrapInstanceLambda DependsOn: - ExampleC9BootstrapInstanceLambdaFunction - ExampleC9Instance - ExampleC9InstanceProfile - ExampleC9LambdaExecutionRole Properties: Tags: - Key: Environment Value: AWS Example ServiceToken: Fn::GetAtt: - ExampleC9BootstrapInstanceLambdaFunction - Arn REGION: Ref: AWS::Region StackName: Ref: AWS::StackName EnvironmentId: Ref: ExampleC9Instance LabIdeInstanceProfileName: Ref: ExampleC9InstanceProfile LabIdeInstanceProfileArn: Fn::GetAtt: - ExampleC9InstanceProfile - Arn ExampleC9BootstrapInstanceLambdaFunction: Type: AWS::Lambda::Function Properties: Tags: - Key: Environment Value: AWS Example Handler: index.lambda_handler Role: Fn::GetAtt: - ExampleC9LambdaExecutionRole - Arn Runtime: python3.9 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): # logger.info('event: {}'.format(event)) # logger.info('context: {}'.format(context)) responseData = {} status = cfnresponse.SUCCESS if event['RequestType'] == 'Delete': responseData = {'Success': 'Custom Resource removed'} cfnresponse.send(event, context, status, responseData, 'CustomResourcePhysicalID') if event['RequestType'] == 'Create': try: # Open AWS clients ec2 = boto3.client('ec2') # Get the InstanceId of the Cloud9 IDE instance = ec2.describe_instances(Filters=[{'Name': 'tag:Name','Values': ['aws-cloud9-'+event['ResourceProperties']['StackName']+'-'+event['ResourceProperties']['EnvironmentId']]}])['Reservations'][0]['Instances'][0] # logger.info('instance: {}'.format(instance)) # Create the IamInstanceProfile request object iam_instance_profile = { 'Arn': event['ResourceProperties']['LabIdeInstanceProfileArn'], 'Name': event['ResourceProperties']['LabIdeInstanceProfileName'] } # logger.info('iam_instance_profile: {}'.format(iam_instance_profile)) # Wait for Instance to become ready before adding Role instance_state = instance['State']['Name'] # logger.info('instance_state: {}'.format(instance_state)) while instance_state != 'running': time.sleep(5) instance_state = ec2.describe_instances(InstanceIds=[instance['InstanceId']]) # logger.info('instance_state: {}'.format(instance_state)) # attach instance profile response = ec2.associate_iam_instance_profile(IamInstanceProfile=iam_instance_profile, InstanceId=instance['InstanceId']) # logger.info('response - associate_iam_instance_profile: {}'.format(response)) r_ec2 = boto3.resource('ec2') responseData = {'Success': 'Started bootstrapping for instance: '+instance['InstanceId']} cfnresponse.send(event, context, status, responseData, 'CustomResourcePhysicalID') except Exception as e: status = cfnresponse.FAILED print(traceback.format_exc()) responseData = {'Error': traceback.format_exc(e)} finally: cfnresponse.send(event, context, status, responseData, 'CustomResourcePhysicalID') ################## SSM BOOTSRAP HANDLER ############### ExampleC9OutputBucket: Type: AWS::S3::Bucket DeletionPolicy: Delete Properties: VersioningConfiguration: Status: Enabled BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 ExampleC9SSMDocument: Type: AWS::SSM::Document Properties: Tags: - Key: Environment Value: AWS Example DocumentType: Command DocumentFormat: YAML Content: schemaVersion: '2.2' description: Bootstrap Cloud9 Instance mainSteps: - action: aws:runShellScript name: ExampleC9bootstrap 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 - echo '=== INSTALL and CONFIGURE default software components ===' - sudo -H -u ec2-user bash -c "pip install --user -U boto boto3 botocore awscli" - echo '=== Resizing the Instance volume' - !Sub SIZE=${ExampleC9InstanceVolumeSize} - !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 - 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 - echo "Bootstrap completed with return code $?" ExampleC9BootstrapAssociation: Type: AWS::SSM::Association DependsOn: ExampleC9OutputBucket Properties: Name: !Ref ExampleC9SSMDocument OutputLocation: S3Location: OutputS3BucketName: !Ref ExampleC9OutputBucket OutputS3KeyPrefix: bootstrapoutput Targets: - Key: tag:SSMBootstrap Values: - Active ################## INSTANCE ##################### ExampleC9InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: "/" Roles: - Ref: ExampleC9Role ExampleC9Instance: Description: "-" DependsOn: ExampleC9BootstrapAssociation Type: AWS::Cloud9::EnvironmentEC2 Properties: Description: AWS Cloud9 instance for Examples AutomaticStopTimeMinutes: 3600 InstanceType: Ref: ExampleC9InstanceType Name: Ref: AWS::StackName OwnerArn: !If [Create3rdPartyResources, !Ref ExampleOwnerArn, !Ref "AWS::NoValue" ] Tags: - Key: SSMBootstrap Value: Active - Key: Environment Value: AWS Example Outputs: Cloud9IDE: Value: Fn::Join: - '' - - https:// - Ref: AWS::Region - ".console.aws.amazon.com/cloud9/ide/" - Ref: ExampleC9Instance - "?region=" - Ref: AWS::Region