Description: Create a CodePipeline to include Nested CloudFormation, CodeBuild and Approval steps. Parameters: CFNTemplateRepoName: Type: String Description: Name of the repo which contains CFN template. ValidateResourcesRepoName: Type: String Description: Name of the repo which contains test script to validate the reosurces. UATTopic: Type: String Description: Name of the SNS topic in same region to send UAT approval notification. ProdTopic: Type: String Description: Name of the SNS topic in same region to send Production approval notification. ArtifactStoreS3Location: Type: String Description: Name of the S3 bucket to store CodePipeline artificat. Resources: CodePipelineRole: Type: "AWS::IAM::Role" Properties: RoleName: Fn::Sub: CodePipelineRole-${AWS::StackName} AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "codepipeline.amazonaws.com" Action: - "sts:AssumeRole" Path: / Policies: - PolicyName: "CodePipelineNestedCFNAccessPolicy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "s3:DeleteObject" - "s3:GetObject" - "s3:GetObjectVersion" - "s3:ListBucket" - "s3:PutObject" - "s3:GetBucketPolicy" Resource: - Fn::Sub: arn:aws:s3:::${ArtifactStoreS3Location} - Fn::Sub: arn:aws:s3:::${ArtifactStoreS3Location}/* - Effect: "Allow" Action: - "sns:Publish" Resource: - Fn::Sub: arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${UATTopic} - Fn::Sub: arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${ProdTopic} - Effect: "Allow" Action: - "codecommit:ListBranches" - "codecommit:ListRepositories" - "codecommit:BatchGetRepositories" - "codecommit:Get*" - "codecommit:GitPull" - "codecommit:UploadArchive" Resource: - Fn::Sub: arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${CFNTemplateRepoName} - Fn::Sub: arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${ValidateResourcesRepoName} - Effect: "Allow" Action: - "cloudformation:CreateChangeSet" - "cloudformation:CreateStack" - "cloudformation:CreateUploadBucket" - "cloudformation:DeleteStack" - "cloudformation:Describe*" - "cloudformation:List*" - "cloudformation:UpdateStack" - "cloudformation:ValidateTemplate" - "cloudformation:ExecuteChangeSet" Resource: - Fn::Sub: arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/Test-${AWS::StackName}* - Fn::Sub: arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/UAT-${AWS::StackName}* - Fn::Sub: arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/Prod-${AWS::StackName}* - Effect: "Allow" Action: - "codebuild:StartBuild" - "codebuild:BatchGetBuilds" Resource: - Fn::Sub: arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/BuildCopyCFN-${AWS::StackName} - Fn::Sub: arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/ValidateResource-${AWS::StackName} - Fn::Sub: arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:build/BuildCopyCFN-${AWS::StackName}:* - Fn::Sub: arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:build/ValidateResource-${AWS::StackName}:* - Effect: "Allow" Action: - "iam:PassRole" Resource: - Fn::Sub: arn:aws:iam::${AWS::AccountId}:role/CloudFormationRole-${AWS::StackName} CloudFormationRole: Type: "AWS::IAM::Role" Properties: RoleName: Fn::Sub: CloudFormationRole-${AWS::StackName} AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "cloudformation.amazonaws.com" Action: - "sts:AssumeRole" Path: / Policies: - PolicyName: "CloudFormationNestedCFNAccessPolicy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "iam:AddRoleToInstanceProfile" - "iam:AttachRolePolicy" - "iam:CreateInstanceProfile" - "iam:CreatePolicy" - "iam:CreateRole" - "iam:DeleteInstanceProfile" - "iam:DeletePolicy" - "iam:DeleteRole" - "iam:DeleteRolePolicy" - "iam:DetachRolePolicy" - "iam:GetInstanceProfile" - "iam:GetPolicy" - "iam:GetRole" - "iam:GetRolePolicy" - "iam:ListAttachedRolePolicies" - "iam:ListInstanceProfiles" - "iam:ListInstanceProfilesForRole" - "iam:ListRolePolicies" - "iam:ListRoles" - "iam:PassRole" - "iam:PutRolePolicy" - "iam:RemoveRoleFromInstanceProfile" Resource: - Fn::Sub: arn:aws:iam::${AWS::AccountId}:role/WebServerIAMRole-* - Effect: "Allow" Action: - "ec2:Describe*" - "ec2:CreateSecurityGroup" - "autoscaling:Describe*" - "elasticloadbalancing:Describe*" - "elasticloadbalancing:CreateLoadBalancer" - "autoscaling:CreateAutoScalingGroup" - "autoscaling:CreateLaunchConfiguration" Resource: - "*" - Effect: "Allow" Action: - "ec2:AttachNetworkInterface" - "ec2:AttachVolume" - "ec2:AuthorizeSecurityGroupIngress" - "ec2:CreateNetworkInterface" - "ec2:CreateTags" - "ec2:CreateVolume" - "ec2:DeleteSecurityGroup" - "ec2:DeleteTags" - "ec2:DeleteVolume" - "ec2:DetachNetworkInterface" - "ec2:DetachVolume" - "ec2:MonitorInstances" - "ec2:RebootInstances" - "ec2:ReleaseAddress" - "ec2:RunInstances" - "ec2:StartInstances" - "ec2:StopInstances" - "ec2:TerminateInstances" - "ec2:UnmonitorInstances" Resource: - Fn::Sub: arn:aws:ec2:${AWS::Region}::image/* - Fn::Sub: arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/* - Fn::Sub: arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:internet-gateway/* - Fn::Sub: arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:key-pair/* - Fn::Sub: arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:network-interface/* - Fn::Sub: arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:security-group/* - Fn::Sub: arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:subnet/* - Fn::Sub: arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:volume/* - Effect: "Allow" Action: - "iam:AddRoleToInstanceProfile" - "iam:CreateInstanceProfile" - "iam:DeleteInstanceProfile" - "iam:GetInstanceProfile" - "iam:ListInstanceProfiles" - "iam:ListInstanceProfilesForRole" - "iam:RemoveRoleFromInstanceProfile" Resource: - Fn::Sub: arn:aws:iam::${AWS::AccountId}:instance-profile/Test-${AWS::StackName}-SecurityStack* - Fn::Sub: arn:aws:iam::${AWS::AccountId}:instance-profile/UAT-${AWS::StackName}-SecurityStack* - Fn::Sub: arn:aws:iam::${AWS::AccountId}:instance-profile/Prod-${AWS::StackName}-SecurityStack* - Effect: "Allow" Action: - "rds:AddTagsToResource" - "rds:CreateDBInstance" - "rds:CreateDBSecurityGroup" - "rds:DeleteDBInstance" - "rds:DeleteDBSecurityGroup" - "rds:DescribeDBInstances" - "rds:DescribeDBParameterGroups" - "rds:DescribeDBParameters" - "rds:DescribeDBSecurityGroups" - "rds:DescribeDBSubnetGroups" - "rds:DescribeOptionGroups" - "rds:ModifyDBInstance" - "rds:RebootDBInstance" - "rds:RemoveTagsFromResource" Resource: - Fn::Sub: arn:aws:rds:${AWS::Region}:${AWS::AccountId}:db:* - Fn::Sub: arn:aws:rds:${AWS::Region}:${AWS::AccountId}:og:* - Fn::Sub: arn:aws:rds:${AWS::Region}:${AWS::AccountId}:pg:* - Fn::Sub: arn:aws:rds:${AWS::Region}:${AWS::AccountId}:secgrp:* - Fn::Sub: arn:aws:rds:${AWS::Region}:${AWS::AccountId}:subgrp:* - Effect: "Allow" Action: - "elasticloadbalancing:AddTags" - "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer" - "elasticloadbalancing:AttachLoadBalancerToSubnets" - "elasticloadbalancing:ConfigureHealthCheck" - "elasticloadbalancing:CreateListener" - "elasticloadbalancing:CreateLoadBalancerListeners" - "elasticloadbalancing:CreateLoadBalancerPolicy" - "elasticloadbalancing:DeleteListener" - "elasticloadbalancing:DeleteLoadBalancer" - "elasticloadbalancing:DeleteLoadBalancerListeners" - "elasticloadbalancing:DeleteLoadBalancerPolicy" - "elasticloadbalancing:DeregisterInstancesFromLoadBalancer" - "elasticloadbalancing:DetachLoadBalancerFromSubnets" - "elasticloadbalancing:ModifyListener" - "elasticloadbalancing:ModifyLoadBalancerAttributes" - "elasticloadbalancing:RegisterInstancesWithLoadBalancer" - "elasticloadbalancing:RemoveTags" - "elasticloadbalancing:SetSecurityGroups" - "elasticloadbalancing:SetSubnets" - "elasticloadbalancing:SetLoadBalancerPoliciesOfListener" - "elasticloadbalancing:EnableAvailabilityZonesForLoadBalancer" - "elasticloadbalancing:DisableAvailabilityZonesForLoadBalancer" Resource: - Fn::Sub: arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:loadbalancer/WebELB-* - Effect: "Allow" Action: - "autoscaling:AttachInstances" - "autoscaling:AttachLoadBalancers" - "autoscaling:CreateOrUpdateTags" - "autoscaling:DeleteAutoScalingGroup" - "autoscaling:DeleteLaunchConfiguration" - "autoscaling:DeleteTags" - "autoscaling:SetDesiredCapacity" - "autoscaling:SetInstanceHealth" - "autoscaling:TerminateInstanceInAutoScalingGroup" - "autoscaling:UpdateAutoScalingGroup" Resource: - Fn::Sub: arn:aws:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*:autoScalingGroupName/Test-${AWS::StackName}-ServerStack-* - Fn::Sub: arn:aws:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*:autoScalingGroupName/UAT-${AWS::StackName}-ServerStack-* - Fn::Sub: arn:aws:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*:autoScalingGroupName/Prod-${AWS::StackName}-ServerStack-* - Fn::Sub: arn:aws:autoscaling:${AWS::Region}:${AWS::AccountId}:launchConfiguration:${AWS::AccountId}:*:Test-${AWS::StackName}-ServerStack-* - Fn::Sub: arn:aws:autoscaling:${AWS::Region}:${AWS::AccountId}:launchConfiguration:${AWS::AccountId}:*:UAT-${AWS::StackName}-ServerStack-* - Fn::Sub: arn:aws:autoscaling:${AWS::Region}:${AWS::AccountId}:launchConfiguration:${AWS::AccountId}:*:Prod-${AWS::StackName}-ServerStack-* - Effect: "Allow" Action: - "s3:GetObject" - "s3:ListBucket" Resource: - Fn::Sub: arn:aws:s3:::${ArtifactStoreS3Location} - Fn::Sub: arn:aws:s3:::${ArtifactStoreS3Location}/* CodeBuildRole: Type: "AWS::IAM::Role" Properties: RoleName: Fn::Sub: CodeBuildRole-${AWS::StackName} AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "codebuild.amazonaws.com" Action: - "sts:AssumeRole" Path: /service-role/ Policies: - PolicyName: "CodeBuildNestedCFNAccessPolicy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "cloudformation:Get*" - "cloudformation:Describe*" - "cloudformation:List*" Resource: - Fn::Sub: arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/Test-${AWS::StackName}* - Effect: "Allow" Action: - "codecommit:ListBranches" - "codecommit:ListRepositories" - "codecommit:BatchGetRepositories" - "codecommit:Get*" - "codecommit:GitPull" Resource: - Fn::Sub: arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${CFNTemplateRepoName} - Fn::Sub: arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${ValidateResourcesRepoName} - Effect: "Allow" Action: - "ec2:Describe*" - "cloudformation:ValidateTemplate" - "elasticloadbalancing:Describe*" - "autoscaling:Describe*" - "iam:Get*" - "iam:List*" - "logs:Describe*" - "logs:Get*" - "tag:Get*" Resource: - "*" - Effect: "Allow" Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: - Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/* - Effect: "Allow" Action: - "s3:PutObject" - "s3:GetObject" - "s3:GetObjectVersion" - "s3:ListBucket" Resource: - Fn::Sub: arn:aws:s3:::codepipeline-${AWS::Region}-* - Fn::Sub: arn:aws:s3:::${ArtifactStoreS3Location}/* - Fn::Sub: arn:aws:s3:::${ArtifactStoreS3Location} BuildCopyCFNProject: Type: AWS::CodeBuild::Project Properties: Name: Fn::Sub: BuildCopyCFN-${AWS::StackName} Description: Build to validate and copy CFN templates ServiceRole: Fn::GetAtt: [ CodeBuildRole, Arn ] Artifacts: Type: S3 Location: Ref: ArtifactStoreS3Location Name: Fn::Sub: BuildCopyCFN-${AWS::StackName} Environment: Type: LINUX_CONTAINER ComputeType: BUILD_GENERAL1_SMALL Image: aws/codebuild/nodejs:7.0.0 EnvironmentVariables: - Name: TEMPLATE_BUCKET Value: Ref: ArtifactStoreS3Location - Name: TEMPLATE_PREFIX Value: codebuild Source: Location: Fn::Sub: https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/${CFNTemplateRepoName} Type: CODECOMMIT TimeoutInMinutes: 15 EncryptionKey: Fn::Sub: arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3 Tags: - Key: Name Value: Fn::Sub: BuildCopyCFN-${AWS::StackName} ValidateResourceProject: Type: AWS::CodeBuild::Project Properties: Name: Fn::Sub: ValidateResource-${AWS::StackName} Description: Test build to validate the resources created by CFN templates ServiceRole: Fn::GetAtt: [ CodeBuildRole, Arn ] Artifacts: Type: S3 Location: Ref: ArtifactStoreS3Location Name: Fn::Sub: ValidateResource-${AWS::StackName} Environment: Type: LINUX_CONTAINER ComputeType: BUILD_GENERAL1_SMALL Image: aws/codebuild/python:2.7.12 Source: Location: Fn::Sub: https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/${ValidateResourcesRepoName} Type: CODECOMMIT TimeoutInMinutes: 15 EncryptionKey: Fn::Sub: arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3 Tags: - Key: Name Value: Fn::Sub: ValidateResource-${AWS::StackName} DeployPipeline: Type: "AWS::CodePipeline::Pipeline" Properties: Name: Fn::Sub: ContinuousDeliveryNestedCFN-${AWS::StackName} RoleArn: Fn::GetAtt: [ CodePipelineRole, Arn ] Stages: - Name: CFNSource Actions: - Name: CFNTemplateSource ActionTypeId: Category: Source Owner: AWS Version: 1 Provider: CodeCommit OutputArtifacts: - Name: CFNTemplateOutput Configuration: BranchName: master RepositoryName: Ref: CFNTemplateRepoName RunOrder: 1 - Name: TestArtifactSource ActionTypeId: Category: Source Owner: AWS Version: 1 Provider: CodeCommit OutputArtifacts: - Name: TestArtifacts Configuration: BranchName: master RepositoryName: Ref: ValidateResourcesRepoName RunOrder: 1 - Name: Validate Actions: - Name: CodeBuild InputArtifacts: - Name: CFNTemplateOutput ActionTypeId: Category: Build Owner: AWS Version: 1 Provider: CodeBuild OutputArtifacts: - Name: CFNTemplateArtifact Configuration: ProjectName: Ref: BuildCopyCFNProject RunOrder: 1 - Name: CreateTestCFNStack InputArtifacts: - Name: CFNTemplateArtifact ActionTypeId: Category: Deploy Owner: AWS Version: 1 Provider: CloudFormation OutputArtifacts: - Name: CreatedTestCFNStack Configuration: ActionMode: CREATE_UPDATE RoleArn: Fn::GetAtt: [ CloudFormationRole, Arn ] Capabilities: CAPABILITY_NAMED_IAM StackName: Fn::Sub: Test-${AWS::StackName} TemplateConfiguration: CFNTemplateArtifact::config-test.json TemplatePath: CFNTemplateArtifact::master-stack.yml RunOrder: 2 - Name: ValidateTestStack InputArtifacts: - Name: TestArtifacts ActionTypeId: Category: Test Owner: AWS Version: 1 Provider: CodeBuild OutputArtifacts: - Name: ValidatedTestStack Configuration: ProjectName: Ref: ValidateResourceProject RunOrder: 3 - Name: DeleteTestStack InputArtifacts: - Name: ValidatedTestStack ActionTypeId: Category: Deploy Owner: AWS Version: 1 Provider: CloudFormation OutputArtifacts: - Name: ProceedToUAT Configuration: StackName: Fn::Sub: Test-${AWS::StackName} ActionMode: DELETE_ONLY RoleArn: Fn::GetAtt: [ CloudFormationRole, Arn ] RunOrder: 4 - Name: UAT Actions: - Name: CreateUATChangeSet InputArtifacts: - Name: CFNTemplateArtifact - Name: ProceedToUAT ActionTypeId: Category: Deploy Owner: AWS Version: 1 Provider: CloudFormation OutputArtifacts: - Name: CreatedUATChangeSet Configuration: ActionMode: CHANGE_SET_REPLACE ChangeSetName: UATCBChangeSet RoleArn: Fn::GetAtt: [ CloudFormationRole, Arn ] Capabilities: CAPABILITY_NAMED_IAM StackName: Fn::Sub: UAT-${AWS::StackName} TemplateConfiguration: CFNTemplateArtifact::config-uat.json TemplatePath: CFNTemplateArtifact::master-stack.yml RunOrder: 1 - Name: ExecuteUATChangeSet InputArtifacts: - Name: CreatedUATChangeSet ActionTypeId: Category: Deploy Owner: AWS Version: 1 Provider: CloudFormation OutputArtifacts: - Name: DeployedUAT Configuration: ActionMode: CHANGE_SET_EXECUTE ChangeSetName: UATCBChangeSet StackName: Fn::Sub: UAT-${AWS::StackName} RunOrder: 2 - Name: UATApproval ActionTypeId: Category: Approval Owner: AWS Version: 1 Provider: Manual Configuration: NotificationArn: Fn::Sub: arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${UATTopic} CustomData: Approve once UAT has been completed. RunOrder: 3 - Name: Production Actions: - Name: CreateProdChangeSet InputArtifacts: - Name: CFNTemplateArtifact ActionTypeId: Category: Deploy Owner: AWS Version: 1 Provider: CloudFormation OutputArtifacts: - Name: CreatedProdChangeSet Configuration: ActionMode: CHANGE_SET_REPLACE ChangeSetName: ProdCBChangeSet RoleArn: Fn::GetAtt: [ CloudFormationRole, Arn ] Capabilities: CAPABILITY_NAMED_IAM StackName: Fn::Sub: Prod-${AWS::StackName} TemplateConfiguration: CFNTemplateArtifact::config-prod.json TemplatePath: CFNTemplateArtifact::master-stack.yml RunOrder: 1 - Name: ProdApproval ActionTypeId: Category: Approval Owner: AWS Version: 1 Provider: Manual Configuration: NotificationArn: Fn::Sub: arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${ProdTopic} CustomData: Approve deployment in production. RunOrder: 2 - Name: DeployProduction InputArtifacts: - Name: CreatedProdChangeSet ActionTypeId: Category: Deploy Owner: AWS Version: 1 Provider: CloudFormation Configuration: ActionMode: CHANGE_SET_EXECUTE ChangeSetName: ProdCBChangeSet StackName: Fn::Sub: Prod-${AWS::StackName} RunOrder: 3 ArtifactStore: Type: S3 Location: Ref: ArtifactStoreS3Location