AWSTemplateFormatVersion: '2010-09-09' Description: Base stack to create resources required for the EKS SaaS Workshop Parameters: # EKS Parameters Cloud9IDEInstanceType: Description: The type of instance to connect to the environment Type: String Default: m5.large KubernetesVersion: Description: Kubernetes version Type: String Default: 1.21 EKSClusterName: Description: Name of EKS Cluster Type: String Default: eksworkshop-eksctl WorkerNodeInstanceType: Description: Worker Node cluster instances Type: String Default: t3.medium Resources: EKSEnvironment: Type: AWS::Cloud9::EnvironmentEC2 DependsOn: [EKSWorkshopRole] Properties: Name : EKS-SaaS-Workshop AutomaticStopTimeMinutes: 900 OwnerArn : !Sub arn:aws:sts::${AWS::AccountId}:assumed-role/TeamRole/MasterKey Description: Cloud9 environment for the EKS SaaS workshop InstanceType: !Ref Cloud9IDEInstanceType Tags: - Key: "run" Value: "aws s3 cp s3://ee-assets-prod-us-east-1/modules/f858611c30174390bceaaa3e6e4b0a6f/v1/envsetup.sh ." EKSWorkshopRole: Type: AWS::IAM::Role Properties: RoleName: eksworkshop-admin AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com - codebuild.amazonaws.com AWS: !Sub arn:aws:iam::${AWS::AccountId}:root Action: - 'sts:AssumeRole' ManagedPolicyArns: - arn:aws:iam::aws:policy/AdministratorAccess EKSEnvironmentInstanceProfile: Type: AWS::IAM::InstanceProfile DependsOn: [EKSWorkshopRole] Properties: Path: / Roles: - eksworkshop-admin KMSSecretsKey: Type: AWS::KMS::Key Properties: Description: "key for EKS secrets encryption" Enabled: true KeyPolicy: Version: '2012-10-17' Id: key-default-1 Statement: - Sid: Enable IAM User Permissions Effect: Allow Principal: AWS: !Sub arn:aws:iam::${AWS::AccountId}:root Action: kms:* Resource: '*' BuildProject: Type: AWS::CodeBuild::Project DependsOn: [EKSEnvironment, EKSEnvironmentInstanceProfile] Properties: Name: !Sub CodeBuild-${AWS::StackName} ServiceRole: !Sub arn:aws:iam::${AWS::AccountId}:role/eksworkshop-admin Artifacts: Type: NO_ARTIFACTS LogsConfig: CloudWatchLogs: Status: ENABLED Environment: Type: LINUX_CONTAINER ComputeType: BUILD_GENERAL1_SMALL Image: aws/codebuild/amazonlinux2-x86_64-standard:1.0 EnvironmentVariables: - Name: CFN_RESPONSE_URL Value: !Ref WaitForStackCreationHandle - Name: CLOUD9_INSTANCE_PROFILE_NAME Value: !Ref EKSEnvironmentInstanceProfile - Name: CLOUD9_ENVIRONMENT_ID Value: !Ref EKSEnvironment - Name: KMS_ARN Value: !GetAtt KMSSecretsKey.Arn Source: Type: NO_SOURCE BuildSpec: !Sub | version: 0.2 phases: install: runtime-versions: python: 3.7 commands: - echo ">>> installed python 3.7" pre_build: commands: - echo ">>> build cluster config" - | cat < cluster-config.yaml apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig #Only use these availability zones availabilityZones: - ${AWS::Region}a - ${AWS::Region}b - ${AWS::Region}c metadata: name: ${EKSClusterName} region: ${AWS::Region} version: "${KubernetesVersion}" cloudWatch: clusterLogging: enableTypes: ["*"] secretsEncryption: keyARN: $KMS_ARN managedNodeGroups: - name: nodegroup instanceType: ${WorkerNodeInstanceType} desiredCapacity: 3 minSize: 2 maxSize: 4 privateNetworking: true volumeSize: 100 volumeType: gp3 volumeEncrypted: true tags: 'eks:cluster-name': ${EKSClusterName} iam: withAddonPolicies: xRay: true cloudWatch: true EOF - echo ">>> install awscli " - pip3 install --upgrade --user awscli - echo ">>> install kubectl" - curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl - chmod +x ./kubectl - curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp - mv -v /tmp/eksctl /usr/local/bin - eksctl version - export PATH=$PWD/:$PATH build: commands: - echo ">>> find instance using environment Id= $CLOUD9_ENVIRONMENT_ID" - CLOUD9_INSTANCE_ID=$(aws ec2 describe-instances --filter Name=tag:aws:cloud9:environment,Values=$CLOUD9_ENVIRONMENT_ID --query Reservations[0].Instances[0].InstanceId --output text) - echo ">>> cloud9 instance id= $CLOUD9_INSTANCE_ID" - echo ">>> assign profile $CLOUD9_INSTANCE_PROFILE_NAME to instance $CLOUD9_INSTANCE_ID" - echo ">>> KMS keyARN = $KMS_ARN" - aws ec2 associate-iam-instance-profile --instance-id $CLOUD9_INSTANCE_ID --iam-instance-profile Name=$CLOUD9_INSTANCE_PROFILE_NAME - eksctl create cluster -f cluster-config.yaml post_build: commands: # CODEBUILD_BUILD_SUCCEEDING = 1 Set to 0 if the build is failing, or 1 if the build is succeeding. - echo ">>> build status $CODEBUILD_BUILD_SUCCEEDING " - | if [ "$CODEBUILD_BUILD_SUCCEEDING" -eq "1" ] then curl -X PUT -H 'Content-Type:' --data-binary '{"Status" : "SUCCESS","Reason" : "Creation Complete", "UniqueId" : "$CODEBUILD_BUILD_ID","Data" : "Creation complete"}' $CFN_RESPONSE_URL else curl -X PUT -H 'Content-Type:' --data-binary '{"Status" : "FAILURE","Reason" : "Creation Failed", "UniqueId" : "$CODEBUILD_BUILD_ID","Data" : "See Codebuild logs for details. $CODEBUILD_LOG_PATH"}' $CFN_RESPONSE_URL fi TimeoutInMinutes: 60 WaitForStackCreationHandle: Type: AWS::CloudFormation::WaitConditionHandle WaitCondition: Type: AWS::CloudFormation::WaitCondition # dont start till we create a lambda function DependsOn: [CustomTriggerBuild] Properties: Handle: !Ref WaitForStackCreationHandle # wait for 55 minutes before giving up Timeout: 3300 # success or failure signal count Count: 1 CustomTriggerBuild: Type: Custom::ManageCloud9IDEIamRole DependsOn: BuildProject Properties: ServiceToken: !GetAtt TriggerBuildLambda.Arn CodebuildProjectName: !Ref BuildProject TriggerBuildLambdaIamRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' ManagedPolicyArns: - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess Policies: - PolicyName: !Sub IAMPolicy-${AWS::StackName} PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - codebuild:* Resource: !GetAtt BuildProject.Arn TriggerBuildLambda: Type: AWS::Lambda::Function Properties: Description: function to retrieve User info Handler: index.handler Role: !GetAtt TriggerBuildLambdaIamRole.Arn Runtime: python3.7 Code: ZipFile: | import boto3 import logging import sys import json import urllib3 logger = logging.getLogger() logger.setLevel(logging.INFO) http = urllib3.PoolManager() codebuild_client = boto3.client('codebuild') # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html def handler(event, context): logger.info('Incoming Event: {0}'.format(event)) response = {} response['PhysicalResourceId'] = 'hardcodedphyscialid' response['StackId'] = event['StackId'] response['RequestId'] = event['RequestId'] response['LogicalResourceId'] = event['LogicalResourceId'] cfn_response_url = event['ResponseURL'] if event['RequestType'] == 'Delete': # return logger.info('Nothing to do. Request Type : {0}'.format(event['RequestType'])) response['Status'] = 'SUCCESS' elif event['RequestType'] == 'Create' or event['RequestType'] == 'Update': try: codebuild_client.start_build(projectName=event['ResourceProperties']['CodebuildProjectName']) response['Status'] = 'SUCCESS' except: logging.error('Error: {0}'.format(sys.exc_info() )) response['Status'] = 'FAILED' http.request('PUT', cfn_response_url, body=json.dumps(response).encode('utf-8'), headers={'Content-Type': 'application/json'}) return 'Done' Outputs: EKSCloud9EnvId: Description: ID of the EKS SaaS Workshop IDE Value: !Sub https://${AWS::Region}.console.aws.amazon.com/cloud9/ide/${EKSEnvironment}?region=${AWS::Region}