AWSTemplateFormatVersion: 2010-09-09 Description: >- Observability Driven Development Transform: - AWS::Serverless-2016-10-31 Globals: Function: Timeout: 600 MemorySize: 256 CodeUri: ./ Environment: Variables: APP_NAME: !Ref ODDTable ODD_TABLE: !Ref ODDTable SERVICE_NAME: item_service ENABLE_DEBUG: false # Enable usage of KeepAlive to reduce overhead of short-lived actions, like DynamoDB queries AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1 Api: TracingEnabled: true Resources: ################## PERMISSIONS AND ROLES ################# ODDC9Role: Type: AWS::IAM::Role Properties: 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: "/" ODDC9LambdaExecutionRole: 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: - '' - - ODDC9LambdaPolicy- - 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 ################ ODDC9BootstrapInstanceLambda: Description: Bootstrap Cloud9 instance Type: Custom::ODDC9BootstrapInstanceLambda DependsOn: - ODDC9BootstrapInstanceLambdaFunction - ODDC9Instance - ODDC9InstanceProfile - ODDC9LambdaExecutionRole Properties: ServiceToken: Fn::GetAtt: - ODDC9BootstrapInstanceLambdaFunction - Arn REGION: Ref: AWS::Region StackName: Ref: AWS::StackName EnvironmentId: Ref: ODDC9Instance LabIdeInstanceProfileName: Ref: ODDC9InstanceProfile LabIdeInstanceProfileArn: Fn::GetAtt: - ODDC9InstanceProfile - Arn ODDC9BootstrapInstanceLambdaFunction: Type: AWS::Serverless::Function Properties: Description: A lmabda function that bootstraps Cloud9. Handler: src/handlers/ODDC9BootstrapInstance.lambda_handler Role: Fn::GetAtt: - ODDC9LambdaExecutionRole - Arn Runtime: python3.9 ################## SSM BOOTSRAP HANDLER ############### ODDC9OutputBucket: Type: AWS::S3::Bucket DeletionPolicy: Delete ODDC9SSMDocument: Type: AWS::SSM::Document Properties: Content: Yaml DocumentType: Command Content: schemaVersion: '2.2' description: Bootstrap Cloud9 Instance mainSteps: - action: aws:runShellScript name: ODDC9bootstrap 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.7 - 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=15 - !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/master/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" - sudo -H -u ec2-user bash -c ". ~/.nvm/nvm.sh && npm install -g artillery" - echo '=== CONFIGURE kubecrtl and setting ENVIRONMENT VARS ===' - 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 ===' - 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 $?" ODDC9BootstrapAssociation: Type: AWS::SSM::Association DependsOn: ODDC9OutputBucket Properties: Name: !Ref ODDC9SSMDocument OutputLocation: S3Location: OutputS3BucketName: !Ref ODDC9OutputBucket OutputS3KeyPrefix: bootstrapoutput Targets: - Key: tag:SSMBootstrap Values: - Active ################## Cloud9 ##################### ODDC9InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: "/" Roles: - Ref: ODDC9Role ODDC9Instance: Description: "-" DependsOn: ODDC9BootstrapAssociation Type: AWS::Cloud9::EnvironmentEC2 Properties: Description: "AWS Cloud9 instance for Observability Driven Development" AutomaticStopTimeMinutes: 3600 InstanceType: "t3.large" Name: Ref: AWS::StackName Tags: - Key: SSMBootstrap Value: Active ################## API Gateway ################## HttpApi: Type: AWS::Serverless::HttpApi Properties: StageName: Prod # Lambda Functions customWidgetDashboardFunction: Type: AWS::Serverless::Function Properties: Runtime: python3.9 Handler: src/handlers/customWidgetDashboard.lambda_handler AutoPublishAlias: live Description: Lambda Function that sends information to CloudWatch Dashboard. Policies: - CloudWatchPutMetricPolicy: {} - CloudWatchLambdaInsightsExecutionRolePolicy - DynamoDBCrudPolicy: TableName: Ref: ODDTable Environment: Variables: DYNAMODB_TABLE_NAME: !Ref ODDTable getByIdFunction: Type: AWS::Serverless::Function Properties: Runtime: nodejs12.x Handler: src/handlers/get-by-id.getByIdHandler Description: A simple ODD includes a HTTP get method to get one item by id from a DynamoDB table. AutoPublishAlias: live Policies: - DynamoDBCrudPolicy: TableName: !Ref ODDTable - CloudWatchPutMetricPolicy: {} - CloudWatchLambdaInsightsExecutionRolePolicy Events: ExplicitApi: Type: HttpApi Properties: ApiId: !Ref HttpApi Path: /items/{id} Method: GET putItemFunction: Type: AWS::Serverless::Function Properties: Runtime: nodejs12.x Handler: src/handlers/put-item.putItemHandler Description: A simple ODD includes a HTTP post method to add one item to a DynamoDB table. AutoPublishAlias: live Policies: - DynamoDBCrudPolicy: TableName: !Ref ODDTable - CloudWatchPutMetricPolicy: {} - SNSPublishMessagePolicy: TopicName: !Sub ${NewItemTopic.TopicName} - CloudWatchLambdaInsightsExecutionRolePolicy Environment: Variables: TOPIC_NAME: !Ref NewItemTopic Events: ExplicitApi: Type: HttpApi Properties: ApiId: !Ref HttpApi Path: /items Method: POST # SNS Topic NewItemTopic: Type: AWS::SNS::Topic # DynamoDB Table ODDTable: Type: AWS::Serverless::SimpleTable Properties: ProvisionedThroughput: ReadCapacityUnits: 10 WriteCapacityUnits: 5 PrimaryKey: Name: id Type: String #CloudWatch Dashboard DashboardSideBySide: Properties: DashboardBody: !Sub - >- { "widgets": [ { "height": 3, "width": 9, "y": 0, "x": 0, "type": "metric", "properties": { "metrics": [ [ "AWS/Lambda", "Invocations", "FunctionName", "${putItemFunction}", "Resource", "${putItemFunction}:live", { "color": "#1f77b4" } ], [ ".", "Errors", ".", ".", ".", ".", { "color": "#d62728" } ] ], "view": "singleValue", "stacked": false, "region": "${AWS::Region}", "stat": "Sum", "period": 60, "legend": { "position": "bottom" }, "setPeriodToTimeRange": true, "title": "putItemMetrics" } }, { "height": 3, "width": 9, "y": 0, "x": 9, "type": "metric", "properties": { "metrics": [ [ "AWS/Lambda", "Invocations", "FunctionName", "${getByIdFunction}", "Resource", "${getByIdFunction}:live" ], [ ".", "Errors", ".", ".", ".", ".", { "color": "#d62728" } ] ], "view": "singleValue", "stacked": false, "region": "${AWS::Region}", "stat": "Sum", "period": 60, "legend": { "position": "bottom" }, "setPeriodToTimeRange": true, "title": "getByIdMetrics" } } ] } - putItemFunction: !Ref putItemFunction getByIdFunction: !Ref getByIdFunction DashboardName: Observability-Dashboard Type: "AWS::CloudWatch::Dashboard" # CloudWatch Logs - Log Groups ApiAccessLogGroup: Type: AWS::Logs::LogGroup DependsOn: HttpApi Properties: LogGroupName: !Sub /aws/apigateway/${HttpApi} RetentionInDays: 7 customWidgetDashboardLogGroup: Type: AWS::Logs::LogGroup DependsOn: customWidgetDashboardFunction Properties: LogGroupName: !Sub /aws/lambda/${customWidgetDashboardFunction} RetentionInDays: 7 GetByIdLogGroup: Type: AWS::Logs::LogGroup DependsOn: getByIdFunction Properties: LogGroupName: !Sub /aws/lambda/${getByIdFunction} RetentionInDays: 7 PutItemLogGroup: Type: AWS::Logs::LogGroup DependsOn: putItemFunction Properties: LogGroupName: !Sub /aws/lambda/${putItemFunction} RetentionInDays: 7 ODDC9BootstrapInstanceLogGroup: Type: AWS::Logs::LogGroup DependsOn: ODDC9BootstrapInstanceLambdaFunction Properties: LogGroupName: !Sub /aws/lambda/${ODDC9BootstrapInstanceLambdaFunction} RetentionInDays: 7 Outputs: HttpApiUrl: Description: "API Gateway endpoint URL for Prod stage" Value: !Sub "https://${HttpApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" ODDTable: Value: !GetAtt ODDTable.Arn Description: ODD Data Table ARN Cloud9IDE: Value: Fn::Join: - '' - - https:// - Ref: AWS::Region - ".console.aws.amazon.com/cloud9/ide/" - Ref: ODDC9Instance - "?region=" - Ref: AWS::Region