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: t2.micro 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. 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 = {} 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, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') except Exception as e: # logger.error(e, exc_info=True) responseData = {'Error': traceback.format_exc(e)} cfnresponse.send(event, context, cfnresponse.FAILED, responseData, 'CustomResourcePhysicalID') ################## SSM BOOTSRAP HANDLER ############### ExampleC9OutputBucket: Type: AWS::S3::Bucket DeletionPolicy: Delete ExampleC9SSMDocument: Type: AWS::SSM::Document Properties: Tags: - Key: Environment Value: AWS Example Content: Yaml DocumentType: Command 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-pip gettext bash-completion - echo '=== CONFIGURE default python version ===' - PATH=$PATH:/usr/bin # This block installs Python3.9.9 and makes it the default (when running "python -V") - sudo yum -y install gcc openssl-devel bzip2-devel libffi-devel - cd /opt - sudo wget https://www.python.org/ftp/python/3.9.9/Python-3.9.9.tgz - sudo tar xzf Python-3.9.9.tgz - cd Python-3.9.9 - sudo ./configure --enable-optimizations - sudo make altinstall - sudo rm -f /opt/Python-3.9.9.tgz - sudo update-alternatives — install /usr/bin/python python /usr/local/bin/python3.9 38000 - sudo update-alternatives — install /usr/bin/python3 python3 /usr/local/bin/python3.9 38000 - sudo ln -sf /usr/local/bin/python3.9 /usr/bin/python - sudo ln -sf /usr/local/bin/python3.9 /usr/bin/python3 # End of Python3.9.9 installation block - alternatives --set python /usr/local/bin/python3.9 - 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=${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 - echo '=== INSTALL and CONFIGURE amplify and setting ENVIRONMENT VARS ===' - sudo -H -u ec2-user bash -c "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash" && sudo -H -u ec2-user bash -c ". ~/.nvm/nvm.sh && nvm install node" && sudo -H -u ec2-user bash -c ". ~/.nvm/nvm.sh && npm install -g @aws-amplify/cli" - echo '=== INSTALL and CONFIGURE IaC Tools ===' - sudo -H -u ec2-user bash -c ". ~/.nvm/nvm.sh && npm install -g aws-cdk" - curl --silent --location -o terraform_0.12.20_linux_amd64.zip https://releases.hashicorp.com/terraform/0.14.7/terraform_0.14.7_linux_amd64.zip && unzip terraform_0.14.7_linux_amd64.zip && mv -f terraform /usr/bin/ #- curl --silent --location -o terraform_0.12.20_linux_amd64.zip https://releases.hashicorp.com/terraform/0.13.5/terraform_0.13.5_linux_amd64.zip && unzip terraform_0.13.5_linux_amd64.zip && mv -f terraform /usr/bin/ - sudo -H -u ec2-user bash -c "pip install --user -U ansible" - echo '=== CONFIGURE kubecrtl and setting ENVIRONMENT VARS ===' - curl --silent --location -o /usr/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl - chmod +x /usr/bin/kubectl - sudo -H -u ec2-user bash -c "kubectl completion bash >> ~/.bash_completion" - sudo -H -u ec2-user bash -c ". /etc/profile.d/bash_completion.sh" - sudo -H -u ec2-user bash -c ". ~/.bash_completion" - echo '=== CONFIGURE eksctl and setting ENVIRONMENT VARS ===' - curl --silent --location "https://github.com/weaveworks/eksctl/releases/download/latest_release/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp - mv /tmp/eksctl /usr/bin - sudo -H -u ec2-user bash -c "eksctl completion bash >> ~/.bash_completion" - sudo -H -u ec2-user bash -c ". /etc/profile.d/bash_completion.sh" - sudo -H -u ec2-user bash -c ". ~/.bash_completion" - 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 - echo "Bootstrap completed with return code $?" ExampleC9BootstrapAssoziation: 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: ExampleC9BootstrapAssoziation 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