--- AWSTemplateFormatVersion: '2010-09-09' Description: AWS CloudFormation template to create a Cloud9 environment and prepare the HTC-Grid setup Metadata: Author: Description: Pierre-Louis Gounod License: Description: 'Copyright 2021 Amazon.com, Inc. and its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.nse.' Parameters: C9InstanceType: Description: Cloud9 instance type Type: String Default: m5.large AllowedValues: - t3.small - t3.medium - m4.large - m5.large ConstraintDescription: Must be a valid Cloud9 instance type C9KubectlVersion: Description: kubectl Version to install on Cloud9 Type: String Default: v1.21.0 ConstraintDescription: Must be a valid kubectl version C9TerraformVersion: Description: Terraform Version to install on Cloud9 Type: String Default: 1.0.0 ConstraintDescription: Must be a valid terraform version C9EKSctlVersion: Description: Cloud9 instance eksctl version Type: String Default: 0.43.0 ConstraintDescription: Must be a valid eksctl version C9HelmVersion: Description: Helm Version to install on Cloud9 Type: String Default: v3.5.3 ConstraintDescription: Must be a valid helm version HTCgridVersion: Description: HTC Grid Version to install on Cloud9 Type: String Default: 0.3.5 ConstraintDescription: Must be a valid HTC Grid version #Used only by Event Engine, if you are self-deploying the stack leave the default value to NONE EETeamRoleArn: Description: "ARN of the Team Role" Default: NONE Type: String ConstraintDescription: This is ONLY used Event Engine, dont change this if you are self-deploying the stack Conditions: NotEventEngine: !Equals [!Ref EETeamRoleArn, NONE] Resources: ################## PERMISSIONS AND ROLES ################# C9Role: Type: AWS::IAM::Role Condition: NotEventEngine 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: "/" C9LambdaExecutionRole: 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:DescribeVolumes - ec2:AssociateIamInstanceProfile - ec2:DescribeIamInstanceProfileAssociations - ec2:ModifyInstanceAttribute - ec2:ModifyVolume - ec2:ReplaceIamInstanceProfileAssociation - iam:ListInstanceProfiles - iam:PassRole Resource: "*" ################## LAMBDA BOOTSTRAP FUNCTION ################ C9BootstrapInstanceLambda: Description: Bootstrap Cloud9 instance Type: Custom::C9BootstrapInstanceLambda DependsOn: - C9BootstrapInstanceLambdaFunction - C9Instance - C9LambdaExecutionRole Properties: Tags: - Key: Environment Value: AWS Example ServiceToken: Fn::GetAtt: - C9BootstrapInstanceLambdaFunction - Arn REGION: Ref: AWS::Region StackName: Ref: AWS::StackName EnvironmentId: Ref: C9Instance LabIdeInstanceProfileArn: !If [ NotEventEngine, !GetAtt C9InstanceProfile.Arn, !Sub 'arn:aws:iam::${AWS::AccountId}:instance-profile/TeamRoleInstanceProfile' ] C9BootstrapInstanceLambdaFunction: Type: AWS::Lambda::Function Properties: Tags: - Key: Environment Value: AWS Example Handler: index.lambda_handler Role: Fn::GetAtt: - C9LambdaExecutionRole - 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(f'event: {event}') print(f'context: {context}') responseData = {} if event['RequestType'] == 'Delete': try: responseData = {'Success': 'Finished cleanup'} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') except Exception as e: responseData = {'Error': traceback.format_exc(e)} cfnresponse.send(event, context, cfnresponse.FAILED, responseData, 'CustomResourcePhysicalID') if event['RequestType'] == 'Create': try: # Open AWS clients ec2 = boto3.client('ec2') # Get the InstanceId from Cloud9 IDE print('tag:aws:cloud9:environment : {}'.format(event['ResourceProperties']['EnvironmentId'])) instance = ec2.describe_instances(Filters=[{'Name': 'tag:aws:cloud9:environment','Values': [event['ResourceProperties']['EnvironmentId']]}])['Reservations'][0]['Instances'][0] print(f'instance: {instance}') volume_id = instance['BlockDeviceMappings'][0]['Ebs']['VolumeId'] print(f'Volume Id {volume_id}') ec2.modify_volume(VolumeId=volume_id, Size=100) print('Changed Volume to 100GB') # Create the IamInstanceProfile request object iam_instance_profile = { 'Arn': event['ResourceProperties']['LabIdeInstanceProfileArn'] } print(f'iam_instance_profile: {iam_instance_profile}') print(f'Will wait for Instance to become ready before adding Role') # Wait for Instance to become ready before adding Role instance_state = instance['State']['Name'] while instance_state != 'running': time.sleep(5) instance_state = ec2.describe_instances(InstanceIds=[instance['InstanceId']]) print(f'Waiting for the instance state to be "running", current instance_state: {instance_state}') print(f'Instance is ready, attaching IAM instance profile: {iam_instance_profile}') # attach instance profile print(f'Instance is running , about to associate iam_instance_profile: {iam_instance_profile}') print(f'Check if there is already an Associated instance profile') try: associationID = ec2.describe_iam_instance_profile_associations( Filters=[ { 'Name': 'instance-id', 'Values': [ instance['InstanceId'], ] }, ], )['IamInstanceProfileAssociations'][0]['AssociationId'] except Exception as e: print(e) associationID= None if associationID: try: print(f'Association found, AssociationID is: {associationID}') print(f'Replacing association') response = ec2.replace_iam_instance_profile_association( IamInstanceProfile=iam_instance_profile, AssociationId=associationID ) print(f'response - associate_iam_instance_profile: {response}') except Exception as e: print(e) else: try: print(f'No existing association. Associating C9 instance profile...') response = ec2.associate_iam_instance_profile(IamInstanceProfile=iam_instance_profile, InstanceId=instance['InstanceId']) print(f'response - associate_iam_instance_profile: {response}') except Exception as e: print(e) responseData = {'Success': 'Started bootstrapping for instance: '+instance['InstanceId']} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') except Exception as e: print(type(e)) print(e) responseData = {'Error': traceback.format_exc(e)} cfnresponse.send(event, context, cfnresponse.FAILED, responseData, 'CustomResourcePhysicalID') ################## SSM BOOTSRAP HANDLER ############### C9OutputBucket: Type: AWS::S3::Bucket DeletionPolicy: Delete C9SSMDocument: 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: C9bootstrap inputs: runCommand: - "#!/bin/bash" - date - . /home/ec2-user/.bashrc - whoami - !Sub 'echo "export KUBECTL_VERSION=${C9KubectlVersion}"' - sudo -H -u ec2-user aws sts get-caller-identity - echo '=== INSTALL kubectl ===' - !Sub 'export KUBECTL_VERSION=${C9KubectlVersion}' - sudo curl --silent --location -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl - sudo chmod +x /usr/local/bin/kubectl - echo '=== Install JQ and envsubst ===' - sudo yum -y install jq gettext unzip - echo '=== Update to the latest AWS CLI ===' - sudo -H -u ec2-user aws --version - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" - unzip awscliv2.zip - sudo ./aws/install - . /home/ec2-user/.bash_profile - sudo -H -u ec2-user aws --version - echo '=== setup AWS configs ===' - rm -vf /home/ec2-user/.aws/credentials - export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account) - export AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region') - echo "export ACCOUNT_ID=${ACCOUNT_ID}" >> /home/ec2-user/.bash_profile - echo "export AWS_REGION=${AWS_REGION}" >> /home/ec2-user/.bash_profile - sudo -H -u ec2-user aws configure set default.region ${AWS_REGION} - sudo -H -u ec2-user aws configure get default.region - sudo -H -u ec2-user aws sts get-caller-identity - echo '=== Install EKSCTL ===' - !Sub 'export EKSCTL_VERSION=${C9EKSctlVersion}' - curl --silent --location "https://github.com/weaveworks/eksctl/releases/download/${EKSCTL_VERSION}/eksctl_Linux_amd64.tar.gz" | tar xz -C /tmp - sudo mv -v /tmp/eksctl /usr/local/bin - echo '=== Install Terraform ===' - !Sub 'export TERRAFORM_VERSION=${C9TerraformVersion}' - curl --silent --location -o /tmp/terraform.zip "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_386.zip" - unzip /tmp/terraform.zip -d /tmp/terraform - sudo chmod +x /tmp/terraform/terraform - sudo mv -v /tmp/terraform/terraform /usr/local/bin/terraform - echo '=== Install HELM ===' - !Sub 'export HELM_VERSION=${C9HelmVersion}' - mkdir -p /tmp/helm - curl --silent --location -o /tmp/helm.tar.gz "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" - tar -zxf /tmp/helm.tar.gz -C /tmp/helm/ - sudo chmod +x /tmp/helm/linux-amd64/helm - sudo cp /tmp/helm/linux-amd64/helm /usr/local/bin/ - echo '=== Increasing Volume Capacity ===' - sudo growpart /dev/nvme0n1 1 - sudo xfs_growfs -d / - echo "=== Downloading HTC-Grid project ===" - !Sub 'export HTCGRID_VERSION=${HTCgridVersion}' - curl --silent --location -o /home/ec2-user/environment/aws-htc-grid.tar.gz "https://github.com/awslabs/aws-htc-grid/archive/refs/tags/v${HTCGRID_VERSION}.tar.gz" - cd /home/ec2-user/environment; tar xzvf aws-htc-grid.tar.gz - mv aws-htc-grid-${HTCGRID_VERSION}/ aws-htc-grid - sudo chown -R ec2-user:ec2-user /home/ec2-user/environment/ C9BootstrapAssociation: Type: AWS::SSM::Association DependsOn: - C9OutputBucket Properties: Name: !Ref C9SSMDocument OutputLocation: S3Location: OutputS3BucketName: !Ref C9OutputBucket OutputS3KeyPrefix: bootstrapoutput Targets: - Key: tag:SSMBootstrap Values: - Active ################## INSTANCE ##################### C9InstanceProfile: Type: AWS::IAM::InstanceProfile Condition: NotEventEngine Properties: Path: "/" Roles: - Ref: C9Role C9Instance: Description: "-" DependsOn: C9BootstrapAssociation Type: AWS::Cloud9::EnvironmentEC2 Properties: Description: AWS Cloud9 instance for Examples AutomaticStopTimeMinutes: 3600 ImageId: amazonlinux-2-x86_64 InstanceType: Ref: C9InstanceType Name: Ref: AWS::StackName OwnerArn: !If [NotEventEngine , !Ref AWS::NoValue , !Sub 'arn:aws:sts::${AWS::AccountId}:assumed-role/TeamRole/MasterKey'] Tags: - Key: SSMBootstrap Value: Active - Key: Environment Value: Ref: AWS::StackName Outputs: Cloud9IDE: Value: Fn::Join: - '' - - https:// - Ref: AWS::Region - ".console.aws.amazon.com/cloud9/ide/" - Ref: C9Instance - "?region=" - Ref: AWS::Region