AWSTemplateFormatVersion: '2010-09-09' Description: This Quick Start deploys CodeBuild, CodePipeline and CodeDeploy for .NET Framework (qs-1qh13h44o) Parameters: GitHubRepositoryName: Type: String Description: GitHub repository name Default: quickstart-dotnet-serverless-cicd GitHubBranchName: Type: String Description: GitHub branch name GitHubOwner: Type: String Description: UserName for GitHUb GitHubOAuthToken: Type: String NoEcho: true Description: OAuthToken for GitHUb SolutionStack: Type: String Description: The platform version for the .Net Elastic Beantalk environment, please check documentation for lastest platfrom version. AllowedValues: - '64bit Windows Server 2016 v2.6.8 running IIS 10.0' - '64bit Windows Server 2019 v2.6.8 running IIS 10.0' Default: '64bit Windows Server 2019 v2.6.8 running IIS 10.0' QSS3BucketName: AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$ ConstraintDescription: This string can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). Default: aws-quickstart Description: S3 bucket name for the Quick Start assets. This name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). Type: String QSS3BucketRegion: Default: 'us-east-1' Description: 'The AWS Region where the Quick Start S3 bucket (QSS3BucketName) is hosted. When using your own bucket, you must specify this value.' Type: String QSS3KeyPrefix: AllowedPattern: ^[0-9a-zA-Z-/]*$ ConstraintDescription: Can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/). Default: quickstart-dotnet-serverless-cicd/ Description: S3 key prefix for the Quick Start assets. This prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/). Type: String Conditions: UsingDefaultBucket: !Equals [!Ref QSS3BucketName, 'aws-quickstart'] Resources: CodeBuildProject: Type: AWS::CodeBuild::Project Properties: Source: Type: CODEPIPELINE InsecureSsl: false Artifacts: Type: CODEPIPELINE Packaging: NONE EncryptionDisabled: false Cache: Type: NO_CACHE Environment: Type: WINDOWS_SERVER_2019_CONTAINER Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${DotNetECR}:latest ComputeType: BUILD_GENERAL1_MEDIUM PrivilegedMode: false ServiceRole: !GetAtt - CodeBuildServiceRole - Arn TimeoutInMinutes: 60 QueuedTimeoutInMinutes: 480 LogsConfig: CloudWatchLogs: Status: ENABLED S3Logs: Status: DISABLED CodePipelineArtifactStoreBucket: Type: 'AWS::S3::Bucket' AppPipeline: Type: 'AWS::CodePipeline::Pipeline' DependsOn: - CodePipelineServiceRole - SSMWaitCondition Properties: RoleArn: !GetAtt - CodePipelineServiceRole - Arn ArtifactStore: Type: S3 Location: !Ref CodePipelineArtifactStoreBucket Stages: - Name: Source Actions: - Name: Source ActionTypeId: Category: Source Owner: ThirdParty Provider: GitHub Version: '1' RunOrder: 1 Configuration: Branch: !Ref GitHubBranchName OAuthToken: !Ref GitHubOAuthToken Owner: !Ref GitHubOwner PollForSourceChanges: false Repo: !Ref GitHubRepositoryName OutputArtifacts: - Name: SourceArtifact - Name: Build Actions: - Name: Build ActionTypeId: Category: Build Owner: AWS Provider: CodeBuild Version: 1 RunOrder: 1 Configuration: ProjectName: !Ref CodeBuildProject OutputArtifacts: - Name: BuildArtifact InputArtifacts: - Name: SourceArtifact - Name: Deploy Actions: - Name: Deploy ActionTypeId: Category: Deploy Owner: AWS Provider: ElasticBeanstalk Version: 1 RunOrder: 1 Configuration: ApplicationName: !Ref ElasticBeanstalkApplication EnvironmentName: !Ref ElasticBeanstalkEnvironment InputArtifacts: - Name: BuildArtifact CodePipelineServiceRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - codepipeline.amazonaws.com Action: 'sts:AssumeRole' Path: / Policies: - PolicyName: !Sub AWS-CodePipeline-Service-${AWS::StackName} PolicyDocument: Version: 2012-10-17 Statement: - Action: - 'iam:PassRole' Resource: '*' Effect: Allow Condition: StringEqualsIfExists: 'iam:PassedToService': - cloudformation.amazonaws.com - elasticbeanstalk.amazonaws.com - ec2.amazonaws.com - ecs-tasks.amazonaws.com - Action: - 'codecommit:CancelUploadArchive' - 'codecommit:GetBranch' - 'codecommit:GetCommit' - 'codecommit:GetUploadArchiveStatus' - 'codecommit:UploadArchive' Resource: '*' Effect: Allow - Action: - 'codedeploy:CreateDeployment' - 'codedeploy:GetApplication' - 'codedeploy:GetApplicationRevision' - 'codedeploy:GetDeployment' - 'codedeploy:GetDeploymentConfig' - 'codedeploy:RegisterApplicationRevision' Resource: '*' Effect: Allow - Action: - 'elasticbeanstalk:*' - 'ec2:*' - 'elasticloadbalancing:*' - 'autoscaling:*' - 'cloudwatch:*' - 's3:*' - 'sns:*' - 'cloudformation:*' - 'rds:*' - 'sqs:*' - 'ecs:*' Resource: '*' Effect: Allow - Action: - 'lambda:InvokeFunction' - 'lambda:ListFunctions' Resource: '*' Effect: Allow - Action: - 'opsworks:CreateDeployment' - 'opsworks:DescribeApps' - 'opsworks:DescribeCommands' - 'opsworks:DescribeDeployments' - 'opsworks:DescribeInstances' - 'opsworks:DescribeStacks' - 'opsworks:UpdateApp' - 'opsworks:UpdateStack' Resource: '*' Effect: Allow - Action: - 'cloudformation:CreateStack' - 'cloudformation:DeleteStack' - 'cloudformation:DescribeStacks' - 'cloudformation:UpdateStack' - 'cloudformation:CreateChangeSet' - 'cloudformation:DeleteChangeSet' - 'cloudformation:DescribeChangeSet' - 'cloudformation:ExecuteChangeSet' - 'cloudformation:SetStackPolicy' - 'cloudformation:ValidateTemplate' Resource: '*' Effect: Allow - Action: - 'codebuild:BatchGetBuilds' - 'codebuild:StartBuild' Resource: '*' Effect: Allow - Effect: Allow Action: - 'devicefarm:ListProjects' - 'devicefarm:ListDevicePools' - 'devicefarm:GetRun' - 'devicefarm:GetUpload' - 'devicefarm:CreateUpload' - 'devicefarm:ScheduleRun' Resource: '*' - Effect: Allow Action: - 'servicecatalog:ListProvisioningArtifacts' - 'servicecatalog:CreateProvisioningArtifact' - 'servicecatalog:DescribeProvisioningArtifact' - 'servicecatalog:DeleteProvisioningArtifact' - 'servicecatalog:UpdateProduct' Resource: '*' - Effect: Allow Action: - 'cloudformation:ValidateTemplate' Resource: '*' - Effect: Allow Action: - 'ecr:DescribeImages' Resource: '*' CodeBuildServiceRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - codebuild.amazonaws.com Action: 'sts:AssumeRole' Path: / Policies: - PolicyName: !Sub AWS-CodeBuild-Service-${AWS::StackName} PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Resource: '*' Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' - Effect: Allow Resource: !Sub arn:aws:s3:::${AWS::StackName}-codepipelineartifactstore* Action: - 's3:PutObject' - 's3:GetObject' - 's3:GetObjectVersion' - 's3:GetBucketAcl' - 's3:GetBucketLocation' EBInstanceProfileRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - 'sts:AssumeRole' Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier - arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker - arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier EBInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref EBInstanceProfileRole ElasticBeanstalkApplication: Type: AWS::ElasticBeanstalk::Application Properties: Description: AWS Elastic Beanstalk Sample Application ElasticBeanstalkEnvironment: Type: AWS::ElasticBeanstalk::Environment DependsOn: ElasticBeanstalkApplication Properties: ApplicationName: !Ref ElasticBeanstalkApplication Description: "AWS Elastic Beanstalk Environment running .Net Application" SolutionStackName: !Ref SolutionStack OptionSettings: - Namespace: 'aws:autoscaling:launchconfiguration' OptionName: IamInstanceProfile Value: !Ref EBInstanceProfile SSMAutomationRole: Type: AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject - s3:ListBucket Resource: "*" Effect: Allow PolicyName: aws-quick-start-s3-policy - PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - cloudformation:SignalResource Resource: !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*' PolicyName: aws-quick-start-cfn-signal-policy - PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - iam:CreateRole - iam:PutRolePolicy - iam:getRolePolicy - iam:DetachRolePolicy - iam:AttachRolePolicy - iam:DeleteRolePolicy - iam:CreateInstanceProfile - iam:DeleteRole - iam:RemoveRoleFromInstanceProfile - iam:AddRoleToInstanceProfile - iam:DeleteInstanceProfile - iam:PassRole Resource: '*' PolicyName: aws-quick-start-create-role-policy Path: / AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ssm.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMFullAccess' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AWSCloudFormationFullAccess' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonEC2FullAccess' LambdaSSMRole: Type: AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - ecr:DescribeImageScanFindings - ecr:BatchGetImage - ecr:DescribeImages - ecr:DescribeRepositories - ecr:BatchDeleteImage - ecr:BatchDeleteImage - ecr:ListImages Resource: !GetAtt 'DotNetECR.Arn' PolicyName: aws-quick-start-delete-ecr-images - PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - iam:PassRole Resource: !GetAtt SSMAutomationRole.Arn PolicyName: QS-SSM-PassRole Path: / AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonSSMAutomationRole' DotNetECR: Type: AWS::ECR::Repository Properties: RepositoryPolicyText: Version: "2012-10-17" Statement: - Sid: AllowPullFromCodeBuild Effect: Allow Principal: Service: codebuild.amazonaws.com Action: - "ecr:GetDownloadUrlForLayer" - "ecr:BatchGetImage" - "ecr:BatchCheckLayerAvailability" GenerateDotNetContainer: Type: AWS::SSM::Document Properties: DocumentType: Automation Content: schemaVersion: "0.3" description: "Create a .NET Framework Container for CodeBuild" assumeRole: "{{AutomationAssumeRole}}" parameters: StackName: default: "" description: "Stack Name Input for cfn resource signal" type: "String" ECRRepoName: default: "" description: "ECR Repo Name" type: "String" QSS3BucketName: default: "aws-quickstart" description: "S3 bucket name for the Quick Start assets. Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-)." type: "String" QSS3BucketRegion: default: "us-east-1" description: "The AWS Region where the Quick Start S3 bucket (QSS3BucketName) is hosted. When using your own bucket, you must specify this value." type: "String" QSS3KeyPrefix: default: "quickstart-dotnet-serverless-cicd/" description: "S3 key prefix for the Quick Start assets. Quick Start key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/)." type: "String" AutomationAssumeRole: default: "" description: "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf." type: "String" mainSteps: - name: createStack action: aws:createStack onFailure: "step:signalfailure" inputs: StackName: !Sub "DotNetContainer-${AWS::StackName}" Capabilities: [ "CAPABILITY_IAM" ] TemplateBody: | Description: "Deploy Instance to Create a Container" Parameters: LatestAmiId: Type: 'AWS::SSM::Parameter::Value' Default: "/aws/service/ami-windows-latest/Windows_Server-2019-English-Core-ContainersLatest" QSS3BucketName: Type: "String" Default: "{{QSS3BucketName}}" Description: "Name of Target S3 Bucket" QSS3BucketRegion: Type: "String" Default: "us-east-1" Description: "The AWS Region where the Quick Start S3 bucket (QSS3BucketName) is hosted. When using your own bucket, you must specify this value." QSS3KeyPrefix: Type: "String" Default: "{{QSS3KeyPrefix}}" Description: "Name of Target S3 Prefix" Resources: DockerCreateRole: Type : AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject Resource: "*" Effect: Allow PolicyName: s3-instance-bucket-policy Path: / ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy' - !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess" - !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonS3FullAccess" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "ec2.amazonaws.com" - "ssm.amazonaws.com" Action: "sts:AssumeRole" IamInstanceProfile: Type: "AWS::IAM::InstanceProfile" Properties: Roles: - !Ref DockerCreateRole EC2Instance: Type: "AWS::EC2::Instance" Properties: ImageId: !Ref LatestAmiId InstanceType: "t3.2xlarge" BlockDeviceMappings: - DeviceName: "/dev/sda1" Ebs: VolumeSize: "50" IamInstanceProfile: !Ref IamInstanceProfile Tags: - Key: "Name" Value: "DotNetFrameworkContainer" - name: "getInstanceId" action: aws:executeAwsApi onFailure: "step:signalfailure" inputs: Service: ec2 Api: DescribeInstances Filters: - Name: "tag:Name" Values: [ "DotNetFrameworkContainer" ] - Name: "instance-state-name" Values: [ "running" ] outputs: - Name: InstanceId Selector: "$.Reservations..Instances..InstanceId" Type: "StringList" - name: "CreateDockerImage" action: "aws:runCommand" onFailure: "step:signalfailure" inputs: DocumentName: "AWS-RunRemoteScript" InstanceIds: - "{{getInstanceId.InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" Parameters: sourceType: "S3" sourceInfo: !Sub - '{"path": "https://${S3Bucket}.s3.${S3Region}.amazonaws.com/{{QSS3KeyPrefix}}scripts/Dockerfile"}' - S3Bucket: !If - UsingDefaultBucket - !Sub '${QSS3BucketName}-${AWS::Region}' - !Ref QSS3BucketName S3Region: !If - UsingDefaultBucket - !Ref AWS::Region - !Ref QSS3BucketRegion commandLine: "docker build -t {{ECRRepoName}}:latest -m 2GB ." - name: "PushDockerImagetoECR" action: aws:runCommand onFailure: "step:signalfailure" inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{getInstanceId.InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" Parameters: commands: - | $Region = (Invoke-RestMethod -Method Get -Uri http://169.254.169.254/latest/dynamic/instance-identity/document).region $AccountID = (Invoke-RestMethod -Method Get -Uri http://169.254.169.254/latest/dynamic/instance-identity/document).accountId $ecrurl = $AccountID + '.dkr.ecr.' + $Region + '.amazonaws.com/{{ECRRepoName}}:latest' Invoke-Expression -Command (Get-ECRLoginCommand -Region $Region).Command docker tag {{ECRRepoName}}:latest $ecrurl docker push $ecrurl # Determines if CFN Needs to be Signaled or if Work flow should just end - name: CFNSignalEnd action: aws:branch inputs: Choices: - NextStep: signalsuccess Not: Variable: "{{StackName}}" StringEquals: "" - NextStep: sleepend Variable: "{{StackName}}" StringEquals: "" # If all steps complete successfully signals CFN of Success - name: "signalsuccess" action: "aws:executeAwsApi" nextStep: "deleteStack" inputs: Service: cloudformation Api: SignalResource LogicalResourceId: "SSMWaitCondition" StackName: "{{StackName}}" Status: SUCCESS UniqueId: "SSMWaitCondition" # If CFN Signl Not Needed this sleep ends work flow - name: "sleepend" action: "aws:sleep" nextStep: "deleteStack" inputs: Duration: PT1S # If any steps fails signals CFN of Failure - name: "signalfailure" action: "aws:executeAwsApi" nextStep: "deleteStack" inputs: Service: cloudformation Api: SignalResource LogicalResourceId: "SSMWaitCondition" StackName: "{{StackName}}" Status: FAILURE UniqueId: "SSMWaitCondition" - name: deleteStack action: aws:deleteStack isEnd: true onFailure: Continue inputs: StackName: !Sub "DotNetContainer-${AWS::StackName}" LambdaSSMExecute: Type: AWS::Lambda::Function Properties: Description: Executes SSM Automation Documents Handler: index.handler Runtime: python3.7 Role: !GetAtt LambdaSSMRole.Arn Timeout: 900 Code: ZipFile: | def handler(event, context): import cfnresponse import boto3, os, json from botocore.vendored import requests ssm_cl = boto3.client('ssm') ecr_cl = boto3.client('ecr') req_type = event['RequestType'] print(event) SUCCESS = "SUCCESS" FAILED = "FAILED" def start_ssmautomation(event): doc_name = event['ResourceProperties']['DocumentName'] ecr_repo = event['ResourceProperties']['ECRRepoName'] stack_name = event['ResourceProperties']['StackName'] ssm_role = event['ResourceProperties']['AutomationAssumeRole'] qs_bucket = event['ResourceProperties']['QSS3BucketName'] qs_region = event['ResourceProperties']['QSS3BucketRegion'] qs_bucket_prefix = event['ResourceProperties']['QSS3KeyPrefix'] start_automation = ssm_cl.start_automation_execution( DocumentName= doc_name, Parameters={ 'ECRRepoName': [ ecr_repo ], 'StackName': [ stack_name ], 'AutomationAssumeRole': [ ssm_role ], 'QSS3BucketName': [ qs_bucket ], 'QSS3BucketRegion': [ qs_region ], 'QSS3KeyPrefix': [ qs_bucket_prefix ] }, ) cfnresponse.send(event, context, SUCCESS, start_automation, start_automation['AutomationExecutionId']) def delete_image(event): ecr_repo = event['ResourceProperties']['ECRRepoName'] delete_image = ecr_cl.batch_delete_image( repositoryName = ecr_repo, imageIds=[ { 'imageTag': 'latest' }, ] ) cfnresponse.send(event, context, SUCCESS, event, 'Image Deleted') actions = { 'Create': start_ssmautomation, 'Delete': delete_image, 'Update': start_ssmautomation } try: actions.get(req_type)(event) except Exception as exc: error_msg = {'Error': '{}'.format(exc)} print(error_msg) cfnresponse.send(event, context, FAILED, error_msg) ExecuteSSMAutomation: Type: Custom::ExecuteSSMAutomation Properties: ServiceToken: !GetAtt LambdaSSMExecute.Arn DocumentName: !Ref GenerateDotNetContainer ECRRepoName: !Ref DotNetECR StackName: !Ref AWS::StackName QSS3BucketName: !Ref QSS3BucketName QSS3BucketRegion: !Ref QSS3BucketRegion QSS3KeyPrefix: !Ref QSS3KeyPrefix AutomationAssumeRole: !GetAtt SSMAutomationRole.Arn SSMWaitHandle: Type: AWS::CloudFormation::WaitConditionHandle SSMWaitCondition: Type: AWS::CloudFormation::WaitCondition CreationPolicy: ResourceSignal: Timeout: PT60M Count: 1 DependsOn: - ExecuteSSMAutomation Properties: Handle: Ref: "SSMWaitHandle" Timeout: "3600" Count: 1 Outputs: CodePipelineName: Description: CodePipeline Name Value: !Ref AppPipeline