From 9cb94dc2a66f54735690f9a3446f3ccf3b4320a0 Mon Sep 17 00:00:00 2001 From: Toby Tipton Date: Thu, 16 Dec 2021 14:44:01 -0500 Subject: [PATCH 1/5] add ECS Deploy Action test to AWS CodePipeline Actions --- .../test/ecs/ecs-deploy-action.test.ts | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/ecs-deploy-action.test.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/ecs-deploy-action.test.ts index 63927d5832ec8..1c02f127112a1 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/ecs-deploy-action.test.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/ecs-deploy-action.test.ts @@ -2,8 +2,10 @@ import '@aws-cdk/assert-internal/jest'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; +import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import * as cpactions from '../../lib'; describe('ecs deploy action', () => { @@ -197,6 +199,99 @@ describe('ecs deploy action', () => { }); }); + describe('ECS deploy Action service reference cross account/region', () => { + test('leverage existing service', () => { + const app = new cdk.App(); + const stack = new PipelineEcsDeployImportStack(app, 'PipelineStack', { + env: { + region: 'us-east-1', + account: '234567890123', + }, + }); + app.synth(); + + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + {}, + { + Actions: [ + { + Name: 'ECS', + ActionTypeId: { + Category: 'Deploy', + Provider: 'ECS', + }, + Configuration: { + ClusterName: 'cluster-name', + ServiceName: 'service-name', + FileName: 'imageFile.json', + }, + Region: 'us-west-2', + RoleArn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::123456789012:role/pipelinestack-support-123ageecsactionrolec7a0155032b56a30da59', + ], + ], + }, + }, + ], + }, + ], + }); + + }); + test('create new service', () => { + const app = new cdk.App(); + const stack = new PipelineEcsDeployCreateStack(app, 'PipelineStack', { + env: { + region: 'us-east-1', + account: '234567890123', + }, + }); + app.synth(); + + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + {}, + { + Actions: [ + { + Name: 'ECS', + ActionTypeId: { + Category: 'Deploy', + Provider: 'ECS', + }, + Configuration: { + ClusterName: 'teststage-ecsstackeecsstackcluster631300db487421429f3b', + ServiceName: 'teststage-ecsstackckfargateservicecb7fe40e2cebe441d9da', + FileName: 'imageFile.json', + }, + Region: 'us-west-2', + RoleArn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::123456789012:role/deployrole', + ], + ], + }, + }, + ], + }, + ], + }); + }); + }); }); function anyEcsService(): ecs.FargateService { @@ -214,3 +309,186 @@ function anyEcsService(): ecs.FargateService { taskDefinition, }); } + +class PipelineEcsDeployBaseStack extends cdk.Stack { + public readonly pipeline: codepipeline.Pipeline; + public readonly artifact: codepipeline.Artifact; + constructor(scope: Construct, id: string, props: cdk.StackProps) { + super(scope, id, props); + const artifact = new codepipeline.Artifact('Artifact'); + this.artifact = artifact; + const bucket = new s3.Bucket(this, 'PipelineBucket', { + versioned: true, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + const source = new cpactions.S3SourceAction({ + actionName: 'Source', + output: artifact, + bucket, + bucketKey: 'key', + }); + this.pipeline = new codepipeline.Pipeline(this, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [source], + }, + ], + }); + this.addStages(); + } + public addStages() { + throw new Error('Not Implemented'); + } +} + +class TestImportStack extends cdk.Stack { + public readonly serviceArn: string; + public readonly clusterName: string; + + constructor(scope: Construct, id: string, props: cdk.StackProps) { + super(scope, id, props); + const vpc = new ec2.Vpc(this, 'Vpc'); + this.clusterName = 'cluster-name'; + const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { + serviceName: 'service-name', + cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { + vpc, + securityGroups: [], + clusterName: this.clusterName, + }), + }); + this.serviceArn = service.serviceArn; + } +} +class TestImportStage extends cdk.Stage { + public readonly serviceArn: string; + public readonly clusterName: string; + + constructor(scope: Construct, id: string, props: cdk.StageProps) { + super(scope, id, props); + const testStack = new TestImportStack(this, 'ecsStack', {}); + this.serviceArn = testStack.serviceArn; + this.clusterName = testStack.clusterName; + } +} + +class PipelineEcsDeployImportStack extends PipelineEcsDeployBaseStack { + public addStages() { + const testStage = new TestImportStage( + this, + 'TestStage', + { + env: { + region: 'us-west-2', + account: '123456789012', + }, + }, + ); + const testIStage = this.pipeline.addStage(testStage); + const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { + serviceArn: testStage.serviceArn, + cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { + vpc: new ec2.Vpc(this, 'Vpc'), + securityGroups: [], + clusterName: testStage.clusterName, + }), + }); + /** + * It is highly recommended passing in the role from the stage/stack, if not a new role + * is created which most likely will not exist in the account you are deploying to. + * The create test has an example of how to leverage that role. + */ + const deployAction = new cpactions.EcsDeployAction({ + actionName: 'ECS', + service: service, + imageFile: this.artifact.atPath('imageFile.json'), + }); + testIStage.addAction(deployAction); + } +} + +class TestCreateStack extends cdk.Stack { + public readonly serviceArn: string; + public readonly clusterName: string; + public readonly deployRole: iam.IRole; + + constructor(scope: Construct, id: string, props: cdk.StackProps) { + super(scope, id, props); + const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition'); + taskDefinition.addContainer('MainContainer', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + const vpc = new ec2.Vpc(this, 'VPC'); + const cluster = new ecs.Cluster(this, 'Cluster', { + clusterName: cdk.PhysicalName.GENERATE_IF_NEEDED, + vpc, + }); + const service = new ecs.FargateService(this, 'FargateService', { + cluster, + taskDefinition, + serviceName: cdk.PhysicalName.GENERATE_IF_NEEDED, + }); + this.serviceArn = this.formatArn({ + service: 'ecs', + resource: 'service', + resourceName: service.serviceName, + }); + this.clusterName = cluster.clusterName; + const deployArn = this.formatArn({ + region: '', + service: 'iam', + resource: 'role', + resourceName: 'deployrole', + }); + this.deployRole = iam.Role.fromRoleArn( + this, + 'DeployRole', + deployArn, + ); + } +} +class TestCreateStage extends cdk.Stage { + public readonly serviceArn: string; + public readonly clusterName: string; + public readonly deployRole: iam.IRole; + + constructor(scope: Construct, id: string, props: cdk.StageProps) { + super(scope, id, props); + const testStack = new TestCreateStack(this, 'ecsStack', {}); + this.serviceArn = testStack.serviceArn; + this.clusterName = testStack.clusterName; + this.deployRole = testStack.deployRole; + } +} + +class PipelineEcsDeployCreateStack extends PipelineEcsDeployBaseStack { + public addStages() { + const testStage = new TestCreateStage( + this, + 'TestStage', + { + env: { + region: 'us-west-2', + account: '123456789012', + }, + }, + ); + const testIStage = this.pipeline.addStage(testStage); + const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { + serviceArn: testStage.serviceArn, + cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { + vpc: new ec2.Vpc(this, 'Vpc'), + securityGroups: [], + clusterName: testStage.clusterName, + }), + }); + const deployAction = new cpactions.EcsDeployAction({ + actionName: 'ECS', + service: service, + imageFile: this.artifact.atPath('imageFile.json'), + role: testStage.deployRole, + }); + testIStage.addAction(deployAction); + } +} \ No newline at end of file From 377ddd1e3c682f896d48094ca3365b9abe497a54 Mon Sep 17 00:00:00 2001 From: Toby Tipton Date: Fri, 17 Dec 2021 08:36:10 -0500 Subject: [PATCH 2/5] move examples to documentation --- .../aws-codepipeline-actions/README.md | 18 + .../test/ecs/ecs-deploy-action.test.ts | 278 ---- ...ecs-create-cross-account.lit.expected.json | 1195 +++++++++++++++++ ...g.pipeline-ecs-create-cross-account.lit.ts | 221 +++ ...ecs-import-cross-account.lit.expected.json | 1195 +++++++++++++++++ ...g.pipeline-ecs-import-cross-account.lit.ts | 208 +++ 6 files changed, 2837 insertions(+), 278 deletions(-) create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.expected.json create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.ts create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.expected.json create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.ts diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md index 7625ed08bfb8e..7e40841682e2f 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/README.md +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -747,6 +747,24 @@ Here's an example: [example ECS pipeline for an application in a separate source code repository](test/integ.pipeline-ecs-separate-source.lit.ts) +#### Deploying to ECS across account or regions with an existing service + +CodePipeline can deploy to an existing ECS service across accounts and/or regions this requires importing of the ECS service in the +account and region it is defined in to get the ARN for the service which then can be used to import the ECS service by the pipeline stack +to determine the correct region for the ECS action. +Here's an example: + +[example ECS pipeline existing ECS service cross account and region](test/integ.pipeline-ecs-import-cross-account.lit.ts) + +#### Deploying to ECS across account or regions with an new service + +CodePipeline can deploy to an new ECS service across accounts and/or regions this requires creating of the ECS service in the +account and region it is defined in and using stackFormat to get the ARN for the service which then can be used to import the +ECS service by the pipeline stack to determine the correct region for the ECS action. +Here's an example: + +[example ECS pipeline new ECS service cross account and region](test/integ.pipeline-ecs-create-cross-account.lit.ts) + ### AWS S3 Deployment To use an S3 Bucket as a deployment target in CodePipeline: diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/ecs-deploy-action.test.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/ecs-deploy-action.test.ts index 1c02f127112a1..63927d5832ec8 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/ecs-deploy-action.test.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/ecs-deploy-action.test.ts @@ -2,10 +2,8 @@ import '@aws-cdk/assert-internal/jest'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; -import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; -import { Construct } from 'constructs'; import * as cpactions from '../../lib'; describe('ecs deploy action', () => { @@ -199,99 +197,6 @@ describe('ecs deploy action', () => { }); }); - describe('ECS deploy Action service reference cross account/region', () => { - test('leverage existing service', () => { - const app = new cdk.App(); - const stack = new PipelineEcsDeployImportStack(app, 'PipelineStack', { - env: { - region: 'us-east-1', - account: '234567890123', - }, - }); - app.synth(); - - expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: [ - {}, - { - Actions: [ - { - Name: 'ECS', - ActionTypeId: { - Category: 'Deploy', - Provider: 'ECS', - }, - Configuration: { - ClusterName: 'cluster-name', - ServiceName: 'service-name', - FileName: 'imageFile.json', - }, - Region: 'us-west-2', - RoleArn: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::123456789012:role/pipelinestack-support-123ageecsactionrolec7a0155032b56a30da59', - ], - ], - }, - }, - ], - }, - ], - }); - - }); - test('create new service', () => { - const app = new cdk.App(); - const stack = new PipelineEcsDeployCreateStack(app, 'PipelineStack', { - env: { - region: 'us-east-1', - account: '234567890123', - }, - }); - app.synth(); - - expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: [ - {}, - { - Actions: [ - { - Name: 'ECS', - ActionTypeId: { - Category: 'Deploy', - Provider: 'ECS', - }, - Configuration: { - ClusterName: 'teststage-ecsstackeecsstackcluster631300db487421429f3b', - ServiceName: 'teststage-ecsstackckfargateservicecb7fe40e2cebe441d9da', - FileName: 'imageFile.json', - }, - Region: 'us-west-2', - RoleArn: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::123456789012:role/deployrole', - ], - ], - }, - }, - ], - }, - ], - }); - }); - }); }); function anyEcsService(): ecs.FargateService { @@ -309,186 +214,3 @@ function anyEcsService(): ecs.FargateService { taskDefinition, }); } - -class PipelineEcsDeployBaseStack extends cdk.Stack { - public readonly pipeline: codepipeline.Pipeline; - public readonly artifact: codepipeline.Artifact; - constructor(scope: Construct, id: string, props: cdk.StackProps) { - super(scope, id, props); - const artifact = new codepipeline.Artifact('Artifact'); - this.artifact = artifact; - const bucket = new s3.Bucket(this, 'PipelineBucket', { - versioned: true, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - const source = new cpactions.S3SourceAction({ - actionName: 'Source', - output: artifact, - bucket, - bucketKey: 'key', - }); - this.pipeline = new codepipeline.Pipeline(this, 'Pipeline', { - stages: [ - { - stageName: 'Source', - actions: [source], - }, - ], - }); - this.addStages(); - } - public addStages() { - throw new Error('Not Implemented'); - } -} - -class TestImportStack extends cdk.Stack { - public readonly serviceArn: string; - public readonly clusterName: string; - - constructor(scope: Construct, id: string, props: cdk.StackProps) { - super(scope, id, props); - const vpc = new ec2.Vpc(this, 'Vpc'); - this.clusterName = 'cluster-name'; - const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { - serviceName: 'service-name', - cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { - vpc, - securityGroups: [], - clusterName: this.clusterName, - }), - }); - this.serviceArn = service.serviceArn; - } -} -class TestImportStage extends cdk.Stage { - public readonly serviceArn: string; - public readonly clusterName: string; - - constructor(scope: Construct, id: string, props: cdk.StageProps) { - super(scope, id, props); - const testStack = new TestImportStack(this, 'ecsStack', {}); - this.serviceArn = testStack.serviceArn; - this.clusterName = testStack.clusterName; - } -} - -class PipelineEcsDeployImportStack extends PipelineEcsDeployBaseStack { - public addStages() { - const testStage = new TestImportStage( - this, - 'TestStage', - { - env: { - region: 'us-west-2', - account: '123456789012', - }, - }, - ); - const testIStage = this.pipeline.addStage(testStage); - const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { - serviceArn: testStage.serviceArn, - cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { - vpc: new ec2.Vpc(this, 'Vpc'), - securityGroups: [], - clusterName: testStage.clusterName, - }), - }); - /** - * It is highly recommended passing in the role from the stage/stack, if not a new role - * is created which most likely will not exist in the account you are deploying to. - * The create test has an example of how to leverage that role. - */ - const deployAction = new cpactions.EcsDeployAction({ - actionName: 'ECS', - service: service, - imageFile: this.artifact.atPath('imageFile.json'), - }); - testIStage.addAction(deployAction); - } -} - -class TestCreateStack extends cdk.Stack { - public readonly serviceArn: string; - public readonly clusterName: string; - public readonly deployRole: iam.IRole; - - constructor(scope: Construct, id: string, props: cdk.StackProps) { - super(scope, id, props); - const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition'); - taskDefinition.addContainer('MainContainer', { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }); - const vpc = new ec2.Vpc(this, 'VPC'); - const cluster = new ecs.Cluster(this, 'Cluster', { - clusterName: cdk.PhysicalName.GENERATE_IF_NEEDED, - vpc, - }); - const service = new ecs.FargateService(this, 'FargateService', { - cluster, - taskDefinition, - serviceName: cdk.PhysicalName.GENERATE_IF_NEEDED, - }); - this.serviceArn = this.formatArn({ - service: 'ecs', - resource: 'service', - resourceName: service.serviceName, - }); - this.clusterName = cluster.clusterName; - const deployArn = this.formatArn({ - region: '', - service: 'iam', - resource: 'role', - resourceName: 'deployrole', - }); - this.deployRole = iam.Role.fromRoleArn( - this, - 'DeployRole', - deployArn, - ); - } -} -class TestCreateStage extends cdk.Stage { - public readonly serviceArn: string; - public readonly clusterName: string; - public readonly deployRole: iam.IRole; - - constructor(scope: Construct, id: string, props: cdk.StageProps) { - super(scope, id, props); - const testStack = new TestCreateStack(this, 'ecsStack', {}); - this.serviceArn = testStack.serviceArn; - this.clusterName = testStack.clusterName; - this.deployRole = testStack.deployRole; - } -} - -class PipelineEcsDeployCreateStack extends PipelineEcsDeployBaseStack { - public addStages() { - const testStage = new TestCreateStage( - this, - 'TestStage', - { - env: { - region: 'us-west-2', - account: '123456789012', - }, - }, - ); - const testIStage = this.pipeline.addStage(testStage); - const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { - serviceArn: testStage.serviceArn, - cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { - vpc: new ec2.Vpc(this, 'Vpc'), - securityGroups: [], - clusterName: testStage.clusterName, - }), - }); - const deployAction = new cpactions.EcsDeployAction({ - actionName: 'ECS', - service: service, - imageFile: this.artifact.atPath('imageFile.json'), - role: testStage.deployRole, - }); - testIStage.addAction(deployAction); - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.expected.json new file mode 100644 index 0000000000000..26bce8e4522fa --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.expected.json @@ -0,0 +1,1195 @@ +[ + { + "Resources": { + "PipelineBucketB967BD35": { + "Type": "AWS::S3::Bucket", + "Properties": { + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineArtifactsBucketEncryptionKey01D58D69": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::234567890123:root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-createpipelinestackpipelinecc1a70d2", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineArtifactsBucket22248F97": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "PipelineRoleD68726F7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleDefaultPolicyC7A05455": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineSourceCodePipelineActionRoleC6F9E7F5", + "Arn" + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::createpipelinestack-suppoeplicationbucket85400285e4a9a6dd80de" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::createpipelinestack-suppoeplicationbucket85400285e4a9a6dd80de/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::123456789012:role/deployrole" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineRoleDefaultPolicyC7A05455", + "Roles": [ + { + "Ref": "PipelineRoleD68726F7" + } + ] + } + }, + "PipelineC660917D": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleD68726F7", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1" + }, + "Configuration": { + "S3Bucket": { + "Ref": "PipelineBucketB967BD35" + }, + "S3ObjectKey": "key" + }, + "Name": "Source", + "OutputArtifacts": [ + { + "Name": "Artifact" + } + ], + "RoleArn": { + "Fn::GetAtt": [ + "PipelineSourceCodePipelineActionRoleC6F9E7F5", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "ECS", + "Version": "1" + }, + "Configuration": { + "ClusterName": "teststage-ecsstackeecsstackclusterc0cbbdd5a4045b3c3e44", + "ServiceName": "teststage-ecsstackckfargateservicee0fd7ddb3c75ddfbf877", + "FileName": "imageFile.json" + }, + "InputArtifacts": [ + { + "Name": "Artifact" + } + ], + "Name": "ECS", + "Region": "us-west-2", + "RoleArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::123456789012:role/deployrole" + ] + ] + }, + "RunOrder": 1 + } + ], + "Name": "TestStage" + } + ], + "ArtifactStores": [ + { + "ArtifactStore": { + "EncryptionKey": { + "Id": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":kms:us-west-2:234567890123:alias/ck-suppotencryptionaliasc4e901c15e32d81a2380" + ] + ] + }, + "Type": "KMS" + }, + "Location": "createpipelinestack-suppoeplicationbucket85400285e4a9a6dd80de", + "Type": "S3" + }, + "Region": "us-west-2" + }, + { + "ArtifactStore": { + "EncryptionKey": { + "Id": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + }, + "Type": "KMS" + }, + "Location": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "Type": "S3" + }, + "Region": "us-east-1" + } + ] + }, + "DependsOn": [ + "PipelineRoleDefaultPolicyC7A05455", + "PipelineRoleD68726F7" + ] + }, + "PipelineSourceCodePipelineActionRoleC6F9E7F5": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::234567890123:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineSourceCodePipelineActionRoleDefaultPolicy2D565925": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineBucketB967BD35", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineBucketB967BD35", + "Arn" + ] + }, + "/key" + ] + ] + } + ] + }, + { + "Action": [ + "s3:DeleteObject*", + "s3:PutObject", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Decrypt" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineSourceCodePipelineActionRoleDefaultPolicy2D565925", + "Roles": [ + { + "Ref": "PipelineSourceCodePipelineActionRoleC6F9E7F5" + } + ] + } + }, + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet3SubnetBE12F0B6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTable93458DBB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTableAssociation1F1EDF02": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + } + } + }, + "VpcPublicSubnet3DefaultRoute4697774F": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet3EIP3A666A23": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3NATGateway7640CD1D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet3EIP3A666A23", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcPrivateSubnet3SubnetF258B56E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableD98824C7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableAssociation16BDDC43": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + } + }, + "VpcPrivateSubnet3DefaultRoute94B74F0D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet3NATGateway7640CD1D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "CreatePipelineStack/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + } + } + }, + { + "Resources": { + "CrossRegionCodePipelineReplicationBucketEncryptionKey70216490": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::234567890123:root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::234567890123:root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::123456789012:role/deployrole" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CrossRegionCodePipelineReplicationBucketEncryptionAliasF1A0F37D": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/ck-suppotencryptionaliasc4e901c15e32d81a2380", + "TargetKeyId": { + "Fn::GetAtt": [ + "CrossRegionCodePipelineReplicationBucketEncryptionKey70216490", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CrossRegionCodePipelineReplicationBucketFC3227F2": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":kms:us-west-2:234567890123:alias/ck-suppotencryptionaliasc4e901c15e32d81a2380" + ] + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "BucketName": "createpipelinestack-suppoeplicationbucket85400285e4a9a6dd80de", + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "CrossRegionCodePipelineReplicationBucketPolicyB7BA2BCA": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "CrossRegionCodePipelineReplicationBucketFC3227F2" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::123456789012:role/deployrole" + ] + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "CrossRegionCodePipelineReplicationBucketFC3227F2", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CrossRegionCodePipelineReplicationBucketFC3227F2", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.ts new file mode 100644 index 0000000000000..3a3d189d14eda --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.ts @@ -0,0 +1,221 @@ +/// !cdk-integ * + +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as cpactions from '../lib'; + + +/** + * This example demonstrates how to create a CodePipeline that deploys to a new ECS Service across + * accounts and regions. + * This will not deploy because integ tests only run in one account. + * Updates to this require yarn integ --dry-run integ.pipeline-ecs-create-cross-account.lit.js to generate the expected JSON file. + */ + +/// !show + +const app = new cdk.App(); + +/** + * This is the Stack which will create an ECS Service and gets deployed to. + */ + +class TestCreateEcsStack extends cdk.Stack { + public readonly serviceArn: string; + public readonly clusterName: string; + public readonly deployRole: iam.IRole; + + constructor(scope: Construct, id: string, props: cdk.StackProps) { + super(scope, id, props); + const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition'); + taskDefinition.addContainer('MainContainer', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + /** + * We are creating the VPC here, if you have VPC already then look it up. + */ + const vpc = new ec2.Vpc(this, 'VPC'); + /** + * The clusterName must be defined. + */ + const cluster = new ecs.Cluster(this, 'Cluster', { + clusterName: cdk.PhysicalName.GENERATE_IF_NEEDED, + vpc, + }); + /** + * The serviceName must be defined. + */ + const service = new ecs.FargateService(this, 'FargateService', { + cluster, + taskDefinition, + serviceName: cdk.PhysicalName.GENERATE_IF_NEEDED, + }); + /** + * Defining the serviceArn using formatArn is required, leveraging service.serviceArn results in a token. + * The token causes in the ECS Deploy Action to not be able to determine region. + * Using the formatArn produces a ARN which only makes the resourceName as a token, which allows + * the ECS Deploy Action to determine the region and account you are deploying to. + */ + this.serviceArn = this.formatArn({ + service: 'ecs', + resource: 'service', + resourceName: service.serviceName, + }); + this.clusterName = cluster.clusterName; + /** + * The deployRole is being looked up from an existing role. + * If you want to create a new role here you can do that however you will need this stack deployed + * before you add the ECSDeployAction to the pipeline. + * + * The role could be created in another pipeline or you could leverage the CDKBootstrap created role. + * + * To leverage the CDKBootstrap created role you would use something like this to define the resourceName + * 'hnb659fds' is the default bootstrap qualifier if you leverage a different qualifer change that. + * const cdkBootstrapQualifier = 'hnb659fds'; + * const deployRoleName = `cdk-${cdkBootstrapQualifier}-deploy-role-${props.env!.account!}-${props.env!.region!}`; + * + * Ensure the role has permissions to deploy to ECS refer to. + * https://docs.aws.amazon.com/codepipeline/latest/userguide/how-to-custom-role.html#how-to-update-role-new-services + */ + const deployArn = this.formatArn({ + region: '', + service: 'iam', + resource: 'role', + resourceName: 'deployrole', + }); + this.deployRole = iam.Role.fromRoleArn( + this, + 'DeployRole', + deployArn, + ); + // Adding ECS Deploy Permissions to deployRole + this.deployRole.addToPrincipalPolicy( + new iam.PolicyStatement({ + actions: [ + 'ecs:DescribeServices', + 'ecs:DescribeTaskDefinition', + 'ecs:DescribeTasks', + 'ecs:ListTasks', + 'ecs:RegisterTaskDefinition', + 'ecs:UpdateService', + ], + resources: ['*'], + }), + ); + + this.deployRole.addToPrincipalPolicy( + new iam.PolicyStatement({ + actions: ['iam:PassRole'], + resources: ['*'], + conditions: { + StringEqualsIfExists: { + 'iam:PassedToService': [ + 'ec2.amazonaws.com', + 'ecs-tasks.amazonaws.com', + ], + }, + }, + }), + ); + + } +} + +/** + * This is the Stage which does our create using {@link TestCreateEcsStack}. + */ +class TestCreateEcsStage extends cdk.Stage { + public readonly serviceArn: string; + public readonly clusterName: string; + public readonly deployRole: iam.IRole; + + constructor(scope: Construct, id: string, props: cdk.StageProps) { + super(scope, id, props); + const testStack = new TestCreateEcsStack(this, 'ecsStack', {}); + this.serviceArn = testStack.serviceArn; + this.clusterName = testStack.clusterName; + this.deployRole = testStack.deployRole; + } +} + +/** + * This is our pipeline which will create an ECS Service and deploy + * to is using {@link EcsDeployAction} using our {@link TestCreateEcsStage} + */ +class PipelineEcsDeployCreateStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: cdk.StackProps) { + super(scope, id, props); + const artifact = new codepipeline.Artifact('Artifact'); + const bucket = new s3.Bucket(this, 'PipelineBucket', { + versioned: true, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + const source = new cpactions.S3SourceAction({ + actionName: 'Source', + output: artifact, + bucket, + bucketKey: 'key', + }); + const pipeline = new codepipeline.Pipeline(this, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [source], + }, + ], + }); + const testStage = new TestCreateEcsStage( + this, + 'TestStage', + { + env: { + region: 'us-west-2', + account: '123456789012', + }, + }, + ); + const testIStage = pipeline.addStage(testStage); + /** + * This imports the service so it can be used by the ECS Deploy Action. + * This must use the serviceArn so that the region is set correctly. + * The VPC doesn't actually matter and it doesn't get created. + * The construct ids will need to be unique. + */ + const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { + serviceArn: testStage.serviceArn, + cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { + vpc: new ec2.Vpc(this, 'Vpc'), + securityGroups: [], + clusterName: testStage.clusterName, + }), + }); + /** + * It is highly recommended passing in the role from the stage/stack, if not a new role + * will be added to the pipeline action, will not exist in the account you are deploying to. + */ + const deployAction = new cpactions.EcsDeployAction({ + actionName: 'ECS', + service: service, + imageFile: artifact.atPath('imageFile.json'), + role: testStage.deployRole, + }); + testIStage.addAction(deployAction); + } +} + +// Define our ECS Deploy Crate Stack related to import and deploy existing services. +new PipelineEcsDeployCreateStack(app, 'CreatePipelineStack', { + env: { + region: 'us-east-1', + account: '234567890123', + }, +}); + +/// !hide + +app.synth(); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.expected.json new file mode 100644 index 0000000000000..59a1b734b8e4a --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.expected.json @@ -0,0 +1,1195 @@ +[ + { + "Resources": { + "PipelineBucketB967BD35": { + "Type": "AWS::S3::Bucket", + "Properties": { + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineArtifactsBucketEncryptionKey01D58D69": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::234567890123:root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-importpipelinestackpipeline916b0bc4", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineArtifactsBucket22248F97": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "PipelineRoleD68726F7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleDefaultPolicyC7A05455": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineSourceCodePipelineActionRoleC6F9E7F5", + "Arn" + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::importpipelinestack-suppoeplicationbucket85400285375f11cdb695" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::importpipelinestack-suppoeplicationbucket85400285375f11cdb695/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::123456789012:role/deployrole" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineRoleDefaultPolicyC7A05455", + "Roles": [ + { + "Ref": "PipelineRoleD68726F7" + } + ] + } + }, + "PipelineC660917D": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleD68726F7", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1" + }, + "Configuration": { + "S3Bucket": { + "Ref": "PipelineBucketB967BD35" + }, + "S3ObjectKey": "key" + }, + "Name": "Source", + "OutputArtifacts": [ + { + "Name": "Artifact" + } + ], + "RoleArn": { + "Fn::GetAtt": [ + "PipelineSourceCodePipelineActionRoleC6F9E7F5", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "ECS", + "Version": "1" + }, + "Configuration": { + "ClusterName": "cluster-name", + "ServiceName": "service-name", + "FileName": "imageFile.json" + }, + "InputArtifacts": [ + { + "Name": "Artifact" + } + ], + "Name": "ECS", + "Region": "us-west-2", + "RoleArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::123456789012:role/deployrole" + ] + ] + }, + "RunOrder": 1 + } + ], + "Name": "TestStage" + } + ], + "ArtifactStores": [ + { + "ArtifactStore": { + "EncryptionKey": { + "Id": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":kms:us-west-2:234567890123:alias/ck-suppotencryptionaliasc4e901c1908c861dd80e" + ] + ] + }, + "Type": "KMS" + }, + "Location": "importpipelinestack-suppoeplicationbucket85400285375f11cdb695", + "Type": "S3" + }, + "Region": "us-west-2" + }, + { + "ArtifactStore": { + "EncryptionKey": { + "Id": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + }, + "Type": "KMS" + }, + "Location": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "Type": "S3" + }, + "Region": "us-east-1" + } + ] + }, + "DependsOn": [ + "PipelineRoleDefaultPolicyC7A05455", + "PipelineRoleD68726F7" + ] + }, + "PipelineSourceCodePipelineActionRoleC6F9E7F5": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::234567890123:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineSourceCodePipelineActionRoleDefaultPolicy2D565925": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineBucketB967BD35", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineBucketB967BD35", + "Arn" + ] + }, + "/key" + ] + ] + } + ] + }, + { + "Action": [ + "s3:DeleteObject*", + "s3:PutObject", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Decrypt" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineSourceCodePipelineActionRoleDefaultPolicy2D565925", + "Roles": [ + { + "Ref": "PipelineSourceCodePipelineActionRoleC6F9E7F5" + } + ] + } + }, + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet3SubnetBE12F0B6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTable93458DBB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTableAssociation1F1EDF02": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + } + } + }, + "VpcPublicSubnet3DefaultRoute4697774F": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet3EIP3A666A23": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3NATGateway7640CD1D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet3EIP3A666A23", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcPrivateSubnet3SubnetF258B56E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "dummy1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableD98824C7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableAssociation16BDDC43": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + } + }, + "VpcPrivateSubnet3DefaultRoute94B74F0D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet3NATGateway7640CD1D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "ImportPipelineStack/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + } + } + }, + { + "Resources": { + "CrossRegionCodePipelineReplicationBucketEncryptionKey70216490": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::234567890123:root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::234567890123:root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::123456789012:role/deployrole" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CrossRegionCodePipelineReplicationBucketEncryptionAliasF1A0F37D": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/ck-suppotencryptionaliasc4e901c1908c861dd80e", + "TargetKeyId": { + "Fn::GetAtt": [ + "CrossRegionCodePipelineReplicationBucketEncryptionKey70216490", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CrossRegionCodePipelineReplicationBucketFC3227F2": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":kms:us-west-2:234567890123:alias/ck-suppotencryptionaliasc4e901c1908c861dd80e" + ] + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "BucketName": "importpipelinestack-suppoeplicationbucket85400285375f11cdb695", + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "CrossRegionCodePipelineReplicationBucketPolicyB7BA2BCA": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "CrossRegionCodePipelineReplicationBucketFC3227F2" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::123456789012:role/deployrole" + ] + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "CrossRegionCodePipelineReplicationBucketFC3227F2", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CrossRegionCodePipelineReplicationBucketFC3227F2", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.ts new file mode 100644 index 0000000000000..53a1a63e8fec7 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.ts @@ -0,0 +1,208 @@ +/// !cdk-integ * + +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as cpactions from '../lib'; + + +/** + * This example demonstrates how to create a CodePipeline that deploy to an existing an ECS Service across + * accounts and regions. + * This will not deploy because integ tests only run in one account. + * Updates to this require yarn integ --dry-run integ.pipeline-ecs-import-cross-account.lit.js to generate the expected JSON file. + */ + +/// !show + +const app = new cdk.App(); + +/** + * Deploying a existing ECS Service using CodePipeline across account(s) and/or region(s). + */ + + +/** + * This is the Stack which will import our existing ECS Service that uses + * the provided clusterName and serviceName. + */ +class TestImportEcsStack extends cdk.Stack { + public readonly serviceArn: string; + public readonly clusterName: string; + public readonly deployRole: iam.IRole; + constructor(scope: Construct, id: string, props: cdk.StackProps) { + super(scope, id, props); + // import the existing VPC + const vpc = ec2.Vpc.fromLookup( + this, + 'VpcLookup', + { + isDefault: false, + }, + ); + this.clusterName = 'cluster-name'; + const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { + serviceName: 'service-name', + cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { + vpc, + securityGroups: [], + clusterName: this.clusterName, + }), + }); + this.serviceArn = service.serviceArn; + /** + * The deployRole is being looked up from an existing role. + * If you want to create a new role here you can do that however you will need this stack deployed + * before you add the ECSDeployAction to the pipeline. + * + * The role could be created in another pipeline or you could leverage the CDKBootstrap created role. + * + * To leverage the CDKBootstrap created role you would use something like this to define the resourceName + * 'hnb659fds' is the default bootstrap qualifier if you leverage a different qualifer change that. + * const cdkBootstrapQualifier = 'hnb659fds'; + * const deployRoleName = `cdk-${cdkBootstrapQualifier}-deploy-role-${props.env!.account!}-${props.env!.region!}`; + * + * Ensure the role has permissions to deploy to ECS refer to. + * https://docs.aws.amazon.com/codepipeline/latest/userguide/how-to-custom-role.html#how-to-update-role-new-services + */ + const deployArn = this.formatArn({ + region: '', + service: 'iam', + resource: 'role', + resourceName: 'deployrole', + }); + this.deployRole = iam.Role.fromRoleArn( + this, + 'DeployRole', + deployArn, + ); + // Adding ECS Deploy Permissions to deployRole + this.deployRole.addToPrincipalPolicy( + new iam.PolicyStatement({ + actions: [ + 'ecs:DescribeServices', + 'ecs:DescribeTaskDefinition', + 'ecs:DescribeTasks', + 'ecs:ListTasks', + 'ecs:RegisterTaskDefinition', + 'ecs:UpdateService', + ], + resources: ['*'], + }), + ); + + this.deployRole.addToPrincipalPolicy( + new iam.PolicyStatement({ + actions: ['iam:PassRole'], + resources: ['*'], + conditions: { + StringEqualsIfExists: { + 'iam:PassedToService': [ + 'ec2.amazonaws.com', + 'ecs-tasks.amazonaws.com', + ], + }, + }, + }), + ); + } +} + +/** + * This is the Stage which does our import using {@link TestImportEcsStack}. + */ +class TestImportEcsStage extends cdk.Stage { + public readonly serviceArn: string; + public readonly clusterName: string; + public readonly deployRole: iam.IRole; + + constructor(scope: Construct, id: string, props: cdk.StageProps) { + super(scope, id, props); + const testStack = new TestImportEcsStack(this, 'ecsStack', {}); + this.serviceArn = testStack.serviceArn; + this.clusterName = testStack.clusterName; + this.deployRole = testStack.deployRole; + } +} + +/** + * This is our pipeline which will import our existing ECS Service and deploy + * to is using {@link EcsDeployAction} using our {@link TestImportEcsStage} + */ +class PipelineEcsDeployImportStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: cdk.StackProps) { + super(scope, id, props); + const artifact = new codepipeline.Artifact('Artifact'); + const bucket = new s3.Bucket(this, 'PipelineBucket', { + versioned: true, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + const source = new cpactions.S3SourceAction({ + actionName: 'Source', + output: artifact, + bucket, + bucketKey: 'key', + }); + const pipeline = new codepipeline.Pipeline(this, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [source], + }, + ], + }); + const testStage = new TestImportEcsStage( + this, + 'TestStage', + { + env: { + region: 'us-west-2', + account: '123456789012', + }, + }, + ); + const testIStage = pipeline.addStage(testStage); + /** + * This imports the service so it can be used by the ECS Deploy Action. + * This must use the serviceArn so that the region is set correctly. + * The VPC doesn't actually matter and it doesn't get created. + * The construct ids will need to be unique. + */ + const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { + serviceArn: testStage.serviceArn, + cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { + vpc: new ec2.Vpc(this, 'Vpc'), + securityGroups: [], + clusterName: testStage.clusterName, + }), + }); + /** + * It is highly recommended passing in the role from the stage/stack, if not a new role + * will be added to the pipeline action, will not exist in the account you are deploying to. + */ + const deployAction = new cpactions.EcsDeployAction({ + actionName: 'ECS', + service: service, + imageFile: artifact.atPath('imageFile.json'), + role: testStage.deployRole, + }); + testIStage.addAction(deployAction); + } +} + +// Define our ECS Deploy Import Stack related to import and deploy existing services. +new PipelineEcsDeployImportStack(app, 'ImportPipelineStack', { + env: { + region: 'us-east-1', + account: '234567890123', + }, +}); + + +/// !hide + +app.synth(); From 27d27df3ca7eeb2bd77083c01c48560a96931ba8 Mon Sep 17 00:00:00 2001 From: tobytipton <89043612+tobytipton@users.noreply.github.com> Date: Tue, 21 Dec 2021 11:12:33 -0500 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Adam Ruka --- .../aws-codepipeline-actions/README.md | 21 ++--- ...g.pipeline-ecs-create-cross-account.lit.ts | 72 +++++++-------- ...g.pipeline-ecs-import-cross-account.lit.ts | 87 ++++++++----------- 3 files changed, 76 insertions(+), 104 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md index 7e40841682e2f..d2f92be512fe5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/README.md +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -747,23 +747,24 @@ Here's an example: [example ECS pipeline for an application in a separate source code repository](test/integ.pipeline-ecs-separate-source.lit.ts) -#### Deploying to ECS across account or regions with an existing service +#### Deploying to an existing ECS service across account and/or regions -CodePipeline can deploy to an existing ECS service across accounts and/or regions this requires importing of the ECS service in the -account and region it is defined in to get the ARN for the service which then can be used to import the ECS service by the pipeline stack -to determine the correct region for the ECS action. +CodePipeline can deploy to an existing ECS service, even across accounts and/or regions. +This requires importing the ECS service in the pipeline stack using the service's full ARN, +which is then used to determine the correct account and region for the ECS Action. Here's an example: -[example ECS pipeline existing ECS service cross account and region](test/integ.pipeline-ecs-import-cross-account.lit.ts) +[example pipeline deploying to an existing ECS service cross account and region](test/integ.ecs-pipeline-cross-region-account-existing.lit.ts) -#### Deploying to ECS across account or regions with an new service +#### Deploying to a new ECS service across account and/or regions -CodePipeline can deploy to an new ECS service across accounts and/or regions this requires creating of the ECS service in the -account and region it is defined in and using stackFormat to get the ARN for the service which then can be used to import the -ECS service by the pipeline stack to determine the correct region for the ECS action. +CodePipeline can also deploy to a new ECS service across accounts and/or regions. +This can be accomplished by using the `Stack.formatArn` method to save the full ARN of the service, +which can then be used to import the ECS service in the pipeline stack, +and the region and account of the ECS Action will be determined from that ARN. Here's an example: -[example ECS pipeline new ECS service cross account and region](test/integ.pipeline-ecs-create-cross-account.lit.ts) +[example pipeline deploying to a new ECS service cross account and region](test/integ.ecs-pipeline-cross-region-account-new.lit.ts) ### AWS S3 Deployment diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.ts index 3a3d189d14eda..19fd7346bbb83 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.ts @@ -74,7 +74,7 @@ class TestCreateEcsStack extends cdk.Stack { * * The role could be created in another pipeline or you could leverage the CDKBootstrap created role. * - * To leverage the CDKBootstrap created role you would use something like this to define the resourceName + * To leverage the CDKBootstrap created role you would use something like this to define the resourceName. * 'hnb659fds' is the default bootstrap qualifier if you leverage a different qualifer change that. * const cdkBootstrapQualifier = 'hnb659fds'; * const deployRoleName = `cdk-${cdkBootstrapQualifier}-deploy-role-${props.env!.account!}-${props.env!.region!}`; @@ -88,40 +88,32 @@ class TestCreateEcsStack extends cdk.Stack { resource: 'role', resourceName: 'deployrole', }); - this.deployRole = iam.Role.fromRoleArn( - this, - 'DeployRole', - deployArn, - ); + this.deployRole = iam.Role.fromRoleArn(this, 'DeployRole', deployArn); // Adding ECS Deploy Permissions to deployRole - this.deployRole.addToPrincipalPolicy( - new iam.PolicyStatement({ - actions: [ - 'ecs:DescribeServices', - 'ecs:DescribeTaskDefinition', - 'ecs:DescribeTasks', - 'ecs:ListTasks', - 'ecs:RegisterTaskDefinition', - 'ecs:UpdateService', - ], - resources: ['*'], - }), - ); - - this.deployRole.addToPrincipalPolicy( - new iam.PolicyStatement({ - actions: ['iam:PassRole'], - resources: ['*'], - conditions: { - StringEqualsIfExists: { - 'iam:PassedToService': [ - 'ec2.amazonaws.com', - 'ecs-tasks.amazonaws.com', - ], - }, + this.deployRole.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: [ + 'ecs:DescribeServices', + 'ecs:DescribeTaskDefinition', + 'ecs:DescribeTasks', + 'ecs:ListTasks', + 'ecs:RegisterTaskDefinition', + 'ecs:UpdateService', + ], + resources: ['*'], + })); + + this.deployRole.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['iam:PassRole'], + resources: ['*'], + conditions: { + StringEqualsIfExists: { + 'iam:PassedToService': [ + 'ec2.amazonaws.com', + 'ecs-tasks.amazonaws.com', + ], }, - }), - ); + }, + })); } } @@ -169,16 +161,12 @@ class PipelineEcsDeployCreateStack extends cdk.Stack { }, ], }); - const testStage = new TestCreateEcsStage( - this, - 'TestStage', - { - env: { - region: 'us-west-2', - account: '123456789012', - }, + const testStage = new TestCreateEcsStage(this, 'TestStage', { + env: { + region: 'us-west-2', + account: '123456789012', }, - ); + }); const testIStage = pipeline.addStage(testStage); /** * This imports the service so it can be used by the ECS Deploy Action. diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.ts index 53a1a63e8fec7..32bc9ca8d3dbc 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.ts @@ -11,8 +11,7 @@ import * as cpactions from '../lib'; /** - * This example demonstrates how to create a CodePipeline that deploy to an existing an ECS Service across - * accounts and regions. + * This example demonstrates how to create a CodePipeline that deploys to an existing ECS Service across accounts and regions. * This will not deploy because integ tests only run in one account. * Updates to this require yarn integ --dry-run integ.pipeline-ecs-import-cross-account.lit.js to generate the expected JSON file. */ @@ -22,7 +21,7 @@ import * as cpactions from '../lib'; const app = new cdk.App(); /** - * Deploying a existing ECS Service using CodePipeline across account(s) and/or region(s). + * Deploying to an existing ECS Service using CodePipeline across account(s) and/or region(s). */ @@ -37,13 +36,9 @@ class TestImportEcsStack extends cdk.Stack { constructor(scope: Construct, id: string, props: cdk.StackProps) { super(scope, id, props); // import the existing VPC - const vpc = ec2.Vpc.fromLookup( - this, - 'VpcLookup', - { - isDefault: false, - }, - ); + const vpc = ec2.Vpc.fromLookup(this, 'VpcLookup', { + isDefault: false, + }); this.clusterName = 'cluster-name'; const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { serviceName: 'service-name', @@ -61,7 +56,7 @@ class TestImportEcsStack extends cdk.Stack { * * The role could be created in another pipeline or you could leverage the CDKBootstrap created role. * - * To leverage the CDKBootstrap created role you would use something like this to define the resourceName + * To leverage the CDKBootstrap created role you would use something like this to define the resourceName. * 'hnb659fds' is the default bootstrap qualifier if you leverage a different qualifer change that. * const cdkBootstrapQualifier = 'hnb659fds'; * const deployRoleName = `cdk-${cdkBootstrapQualifier}-deploy-role-${props.env!.account!}-${props.env!.region!}`; @@ -75,40 +70,32 @@ class TestImportEcsStack extends cdk.Stack { resource: 'role', resourceName: 'deployrole', }); - this.deployRole = iam.Role.fromRoleArn( - this, - 'DeployRole', - deployArn, - ); + this.deployRole = iam.Role.fromRoleArn(this, 'DeployRole', deployArn); // Adding ECS Deploy Permissions to deployRole - this.deployRole.addToPrincipalPolicy( - new iam.PolicyStatement({ - actions: [ - 'ecs:DescribeServices', - 'ecs:DescribeTaskDefinition', - 'ecs:DescribeTasks', - 'ecs:ListTasks', - 'ecs:RegisterTaskDefinition', - 'ecs:UpdateService', - ], - resources: ['*'], - }), - ); - - this.deployRole.addToPrincipalPolicy( - new iam.PolicyStatement({ - actions: ['iam:PassRole'], - resources: ['*'], - conditions: { - StringEqualsIfExists: { - 'iam:PassedToService': [ - 'ec2.amazonaws.com', - 'ecs-tasks.amazonaws.com', - ], - }, + this.deployRole.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: [ + 'ecs:DescribeServices', + 'ecs:DescribeTaskDefinition', + 'ecs:DescribeTasks', + 'ecs:ListTasks', + 'ecs:RegisterTaskDefinition', + 'ecs:UpdateService', + ], + resources: ['*'], + })); + + this.deployRole.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['iam:PassRole'], + resources: ['*'], + conditions: { + StringEqualsIfExists: { + 'iam:PassedToService': [ + 'ec2.amazonaws.com', + 'ecs-tasks.amazonaws.com', + ], }, - }), - ); + }, + })); } } @@ -155,16 +142,12 @@ class PipelineEcsDeployImportStack extends cdk.Stack { }, ], }); - const testStage = new TestImportEcsStage( - this, - 'TestStage', - { - env: { - region: 'us-west-2', - account: '123456789012', - }, + const testStage = new TestImportEcsStage(this, 'TestStage', { + env: { + region: 'us-west-2', + account: '123456789012', }, - ); + }); const testIStage = pipeline.addStage(testStage); /** * This imports the service so it can be used by the ECS Deploy Action. From d2662627c091723330af2a684647a03d6237d167 Mon Sep 17 00:00:00 2001 From: Toby Tipton Date: Tue, 21 Dec 2021 14:03:32 -0500 Subject: [PATCH 4/5] address stage issue and reduce pipeline duplication --- ...s-pipeline-cross-region-account-helpers.ts | 39 ++ ...region-account-existing.lit.expected.json} | 431 +++++++++++++++-- ...line-cross-region-account-existing.lit.ts} | 150 +++--- ...ross-region-account-new.lit.expected.json} | 433 ++++++++++++++++-- ...-pipeline-cross-region-account-new.lit.ts} | 144 +++--- 5 files changed, 969 insertions(+), 228 deletions(-) create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/ecs-pipeline-cross-region-account-helpers.ts rename packages/@aws-cdk/aws-codepipeline-actions/test/{integ.pipeline-ecs-import-cross-account.lit.expected.json => integ.ecs-pipeline-cross-region-account-existing.lit.expected.json} (69%) rename packages/@aws-cdk/aws-codepipeline-actions/test/{integ.pipeline-ecs-import-cross-account.lit.ts => integ.ecs-pipeline-cross-region-account-existing.lit.ts} (55%) rename packages/@aws-cdk/aws-codepipeline-actions/test/{integ.pipeline-ecs-create-cross-account.lit.expected.json => integ.ecs-pipeline-cross-region-account-new.lit.expected.json} (69%) rename packages/@aws-cdk/aws-codepipeline-actions/test/{integ.pipeline-ecs-create-cross-account.lit.ts => integ.ecs-pipeline-cross-region-account-new.lit.ts} (61%) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/ecs-pipeline-cross-region-account-helpers.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/ecs-pipeline-cross-region-account-helpers.ts new file mode 100644 index 0000000000000..553d840217ff8 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/ecs-pipeline-cross-region-account-helpers.ts @@ -0,0 +1,39 @@ +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as cpactions from '../lib'; + +/** + * This is our pipeline which has initial actions. + */ +export class EcsServiceCrossRegionAccountPipelineStack extends cdk.Stack { + public artifact: codepipeline.Artifact; + public pipeline: codepipeline.Pipeline; + + constructor(scope: Construct, id: string, props: cdk.StackProps) { + super(scope, id, props); + + const artifact = new codepipeline.Artifact('Artifact'); + this.artifact = artifact; + const bucket = new s3.Bucket(this, 'PipelineBucket', { + versioned: true, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + const source = new cpactions.S3SourceAction({ + actionName: 'Source', + output: artifact, + bucket, + bucketKey: 'key', + }); + this.pipeline = new codepipeline.Pipeline(this, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [source], + }, + ], + }); + + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.expected.json similarity index 69% rename from packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.expected.json index 59a1b734b8e4a..0bdf5188496c5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.expected.json @@ -28,7 +28,7 @@ { "Ref": "AWS::Partition" }, - ":iam::234567890123:root" + ":iam::pipeline-account:root" ] ] } @@ -45,7 +45,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-importpipelinestackpipeline916b0bc4", + "AliasName": "alias/codepipeline-existingecsservicepipelinestackpipelinef8304f40", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", @@ -165,6 +165,26 @@ ] } }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineTestStagePrepareChangesCodePipelineActionRole96F79520", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineTestStageExecuteChangesCodePipelineActionRole92A91B69", + "Arn" + ] + } + }, { "Action": [ "s3:GetObject*", @@ -184,7 +204,7 @@ { "Ref": "AWS::Partition" }, - ":s3:::importpipelinestack-suppoeplicationbucket85400285375f11cdb695" + ":s3:::existingecsservicepipelineplicationbucketaedc716a96aab89ec381" ] ] }, @@ -196,7 +216,7 @@ { "Ref": "AWS::Partition" }, - ":s3:::importpipelinestack-suppoeplicationbucket85400285375f11cdb695/*" + ":s3:::existingecsservicepipelineplicationbucketaedc716a96aab89ec381/*" ] ] } @@ -224,7 +244,7 @@ { "Ref": "AWS::Partition" }, - ":iam::123456789012:role/deployrole" + ":iam::service-account:role/deployrole" ] ] } @@ -284,6 +304,61 @@ }, { "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "StackName": "TestStage-ecsStack", + "Capabilities": "CAPABILITY_NAMED_IAM", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineTestStagePrepareChangesRoleB43D8DC1", + "Arn" + ] + }, + "ActionMode": "CHANGE_SET_REPLACE", + "ChangeSetName": "changeset-TestStage-ecsStack", + "TemplatePath": "Artifact::ExistingEcsServicePipelineStackTestStageecsStack6FB39135.template.json" + }, + "InputArtifacts": [ + { + "Name": "Artifact" + } + ], + "Name": "PrepareChanges", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineTestStagePrepareChangesCodePipelineActionRole96F79520", + "Arn" + ] + }, + "RunOrder": 1 + }, + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "StackName": "TestStage-ecsStack", + "ActionMode": "CHANGE_SET_EXECUTE", + "ChangeSetName": "changeset-TestStage-ecsStack" + }, + "Name": "ExecuteChanges", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineTestStageExecuteChangesCodePipelineActionRole92A91B69", + "Arn" + ] + }, + "RunOrder": 2 + }, { "ActionTypeId": { "Category": "Deploy", @@ -293,8 +368,7 @@ }, "Configuration": { "ClusterName": "cluster-name", - "ServiceName": "service-name", - "FileName": "imageFile.json" + "ServiceName": "service-name" }, "InputArtifacts": [ { @@ -302,7 +376,7 @@ } ], "Name": "ECS", - "Region": "us-west-2", + "Region": "service-region", "RoleArn": { "Fn::Join": [ "", @@ -311,11 +385,11 @@ { "Ref": "AWS::Partition" }, - ":iam::123456789012:role/deployrole" + ":iam::service-account:role/deployrole" ] ] }, - "RunOrder": 1 + "RunOrder": 3 } ], "Name": "TestStage" @@ -333,16 +407,16 @@ { "Ref": "AWS::Partition" }, - ":kms:us-west-2:234567890123:alias/ck-suppotencryptionaliasc4e901c1908c861dd80e" + ":kms:service-region:pipeline-account:alias/epipelintencryptionalias6398ac74ff79104bcf6e" ] ] }, "Type": "KMS" }, - "Location": "importpipelinestack-suppoeplicationbucket85400285375f11cdb695", + "Location": "existingecsservicepipelineplicationbucketaedc716a96aab89ec381", "Type": "S3" }, - "Region": "us-west-2" + "Region": "service-region" }, { "ArtifactStore": { @@ -360,7 +434,7 @@ }, "Type": "S3" }, - "Region": "us-east-1" + "Region": "pipeline-region" } ] }, @@ -386,7 +460,7 @@ { "Ref": "AWS::Partition" }, - ":iam::234567890123:root" + ":iam::pipeline-account:root" ] ] } @@ -488,6 +562,279 @@ ] } }, + "PipelineTestStagePrepareChangesCodePipelineActionRole96F79520": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::pipeline-account:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineTestStagePrepareChangesCodePipelineActionRoleDefaultPolicy815C349E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineTestStagePrepareChangesRoleB43D8DC1", + "Arn" + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + { + "Action": [ + "cloudformation:CreateChangeSet", + "cloudformation:DeleteChangeSet", + "cloudformation:DescribeChangeSet", + "cloudformation:DescribeStacks" + ], + "Condition": { + "StringEqualsIfExists": { + "cloudformation:ChangeSetName": "changeset-TestStage-ecsStack" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":cloudformation:pipeline-region:pipeline-account:stack/TestStage-ecsStack/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineTestStagePrepareChangesCodePipelineActionRoleDefaultPolicy815C349E", + "Roles": [ + { + "Ref": "PipelineTestStagePrepareChangesCodePipelineActionRole96F79520" + } + ] + } + }, + "PipelineTestStagePrepareChangesRoleB43D8DC1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "cloudformation.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineTestStagePrepareChangesRoleDefaultPolicy56424C45": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + { + "Action": "*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineTestStagePrepareChangesRoleDefaultPolicy56424C45", + "Roles": [ + { + "Ref": "PipelineTestStagePrepareChangesRoleB43D8DC1" + } + ] + } + }, + "PipelineTestStageExecuteChangesCodePipelineActionRole92A91B69": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::pipeline-account:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineTestStageExecuteChangesCodePipelineActionRoleDefaultPolicy30A1554D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cloudformation:DescribeChangeSet", + "cloudformation:DescribeStacks", + "cloudformation:ExecuteChangeSet" + ], + "Condition": { + "StringEqualsIfExists": { + "cloudformation:ChangeSetName": "changeset-TestStage-ecsStack" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":cloudformation:pipeline-region:pipeline-account:stack/TestStage-ecsStack/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineTestStageExecuteChangesCodePipelineActionRoleDefaultPolicy30A1554D", + "Roles": [ + { + "Ref": "PipelineTestStageExecuteChangesCodePipelineActionRole92A91B69" + } + ] + } + }, "Vpc8378EB38": { "Type": "AWS::EC2::VPC", "Properties": { @@ -498,7 +845,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc" + "Value": "ExistingEcsServicePipelineStack/Vpc" } ] } @@ -523,7 +870,7 @@ }, { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet1" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet1" } ] } @@ -537,7 +884,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet1" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet1" } ] } @@ -575,7 +922,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet1" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet1" } ] } @@ -595,7 +942,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet1" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet1" } ] } @@ -620,7 +967,7 @@ }, { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet2" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet2" } ] } @@ -634,7 +981,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet2" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet2" } ] } @@ -672,7 +1019,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet2" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet2" } ] } @@ -692,7 +1039,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet2" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet2" } ] } @@ -717,7 +1064,7 @@ }, { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet3" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet3" } ] } @@ -731,7 +1078,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet3" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet3" } ] } @@ -769,7 +1116,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet3" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet3" } ] } @@ -789,7 +1136,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PublicSubnet3" + "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet3" } ] } @@ -814,7 +1161,7 @@ }, { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PrivateSubnet1" + "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet1" } ] } @@ -828,7 +1175,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PrivateSubnet1" + "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet1" } ] } @@ -876,7 +1223,7 @@ }, { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PrivateSubnet2" + "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet2" } ] } @@ -890,7 +1237,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PrivateSubnet2" + "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet2" } ] } @@ -938,7 +1285,7 @@ }, { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PrivateSubnet3" + "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet3" } ] } @@ -952,7 +1299,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc/PrivateSubnet3" + "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet3" } ] } @@ -986,7 +1333,7 @@ "Tags": [ { "Key": "Name", - "Value": "ImportPipelineStack/Vpc" + "Value": "ExistingEcsServicePipelineStack/Vpc" } ] } @@ -1023,7 +1370,7 @@ { "Ref": "AWS::Partition" }, - ":iam::234567890123:root" + ":iam::pipeline-account:root" ] ] } @@ -1048,7 +1395,7 @@ { "Ref": "AWS::Partition" }, - ":iam::234567890123:root" + ":iam::pipeline-account:root" ] ] } @@ -1070,7 +1417,7 @@ { "Ref": "AWS::Partition" }, - ":iam::123456789012:role/deployrole" + ":iam::service-account:role/deployrole" ] ] } @@ -1087,7 +1434,7 @@ "CrossRegionCodePipelineReplicationBucketEncryptionAliasF1A0F37D": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/ck-suppotencryptionaliasc4e901c1908c861dd80e", + "AliasName": "alias/epipelintencryptionalias6398ac74ff79104bcf6e", "TargetKeyId": { "Fn::GetAtt": [ "CrossRegionCodePipelineReplicationBucketEncryptionKey70216490", @@ -1113,7 +1460,7 @@ { "Ref": "AWS::Partition" }, - ":kms:us-west-2:234567890123:alias/ck-suppotencryptionaliasc4e901c1908c861dd80e" + ":kms:service-region:pipeline-account:alias/epipelintencryptionalias6398ac74ff79104bcf6e" ] ] }, @@ -1122,7 +1469,7 @@ } ] }, - "BucketName": "importpipelinestack-suppoeplicationbucket85400285375f11cdb695", + "BucketName": "existingecsservicepipelineplicationbucketaedc716a96aab89ec381", "PublicAccessBlockConfiguration": { "BlockPublicAcls": true, "BlockPublicPolicy": true, @@ -1157,7 +1504,7 @@ { "Ref": "AWS::Partition" }, - ":iam::123456789012:role/deployrole" + ":iam::service-account:role/deployrole" ] ] } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.ts similarity index 55% rename from packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.ts index 32bc9ca8d3dbc..da1b80792b0e6 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-import-cross-account.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.ts @@ -4,16 +4,14 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; -import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as cpactions from '../lib'; - - +import { EcsServiceCrossRegionAccountPipelineStack } from './ecs-pipeline-cross-region-account-helpers'; /** * This example demonstrates how to create a CodePipeline that deploys to an existing ECS Service across accounts and regions. * This will not deploy because integ tests only run in one account. - * Updates to this require yarn integ --dry-run integ.pipeline-ecs-import-cross-account.lit.js to generate the expected JSON file. + * Updates to this require yarn integ --dry-run integ.ecs-pipeline-cross-region-account-existing.lit.js to generate the expected JSON file. */ /// !show @@ -29,12 +27,14 @@ const app = new cdk.App(); * This is the Stack which will import our existing ECS Service that uses * the provided clusterName and serviceName. */ -class TestImportEcsStack extends cdk.Stack { +class ExistingEcsServiceStack extends cdk.Stack { public readonly serviceArn: string; public readonly clusterName: string; public readonly deployRole: iam.IRole; + constructor(scope: Construct, id: string, props: cdk.StackProps) { super(scope, id, props); + // import the existing VPC const vpc = ec2.Vpc.fromLookup(this, 'VpcLookup', { isDefault: false, @@ -90,8 +90,8 @@ class TestImportEcsStack extends cdk.Stack { conditions: { StringEqualsIfExists: { 'iam:PassedToService': [ - 'ec2.amazonaws.com', - 'ecs-tasks.amazonaws.com', + 'ec2.amazonaws.com', + 'ecs-tasks.amazonaws.com', ], }, }, @@ -100,16 +100,19 @@ class TestImportEcsStack extends cdk.Stack { } /** - * This is the Stage which does our import using {@link TestImportEcsStack}. + * This is the Stage which does our import using {@link ExistingEcsServiceStack}. */ -class TestImportEcsStage extends cdk.Stage { +class ExistingEcsServiceStage extends cdk.Stage { public readonly serviceArn: string; public readonly clusterName: string; public readonly deployRole: iam.IRole; + public readonly stack: cdk.Stack constructor(scope: Construct, id: string, props: cdk.StageProps) { super(scope, id, props); - const testStack = new TestImportEcsStack(this, 'ecsStack', {}); + + const testStack = new ExistingEcsServiceStack(this, 'ecsStack', {}); + this.stack = testStack; this.serviceArn = testStack.serviceArn; this.clusterName = testStack.clusterName; this.deployRole = testStack.deployRole; @@ -117,74 +120,75 @@ class TestImportEcsStage extends cdk.Stage { } /** - * This is our pipeline which will import our existing ECS Service and deploy - * to is using {@link EcsDeployAction} using our {@link TestImportEcsStage} + * This is our pipeline which will create an ECS Service and deploy + * to is using {@link EcsDeployAction} using our {@link NewEcsServiceStage} */ -class PipelineEcsDeployImportStack extends cdk.Stack { - constructor(scope: Construct, id: string, props: cdk.StackProps) { - super(scope, id, props); - const artifact = new codepipeline.Artifact('Artifact'); - const bucket = new s3.Bucket(this, 'PipelineBucket', { - versioned: true, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - const source = new cpactions.S3SourceAction({ - actionName: 'Source', - output: artifact, - bucket, - bucketKey: 'key', - }); - const pipeline = new codepipeline.Pipeline(this, 'Pipeline', { - stages: [ - { - stageName: 'Source', - actions: [source], - }, - ], - }); - const testStage = new TestImportEcsStage(this, 'TestStage', { - env: { - region: 'us-west-2', - account: '123456789012', - }, - }); - const testIStage = pipeline.addStage(testStage); - /** - * This imports the service so it can be used by the ECS Deploy Action. - * This must use the serviceArn so that the region is set correctly. - * The VPC doesn't actually matter and it doesn't get created. - * The construct ids will need to be unique. - */ - const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { - serviceArn: testStage.serviceArn, - cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { - vpc: new ec2.Vpc(this, 'Vpc'), - securityGroups: [], - clusterName: testStage.clusterName, - }), - }); - /** - * It is highly recommended passing in the role from the stage/stack, if not a new role - * will be added to the pipeline action, will not exist in the account you are deploying to. - */ - const deployAction = new cpactions.EcsDeployAction({ - actionName: 'ECS', - service: service, - imageFile: artifact.atPath('imageFile.json'), - role: testStage.deployRole, - }); - testIStage.addAction(deployAction); - } -} - -// Define our ECS Deploy Import Stack related to import and deploy existing services. -new PipelineEcsDeployImportStack(app, 'ImportPipelineStack', { +const pipelineStack = new EcsServiceCrossRegionAccountPipelineStack(app, 'ExistingEcsServicePipelineStack', { env: { - region: 'us-east-1', - account: '234567890123', + region: 'pipeline-region', + account: 'pipeline-account', }, }); +const pipeline: codepipeline.Pipeline = pipelineStack.pipeline; +const artifact: codepipeline.Artifact = pipelineStack.artifact; +const testStage = new ExistingEcsServiceStage(pipelineStack, 'TestStage', { + env: { + region: 'service-region', + account: 'service-account', + }, +}); +const stackName = testStage.stack.stackName; +const changeSetName = `changeset-${testStage.stack.stackName}`; +const stageActions = [ + new cpactions.CloudFormationCreateReplaceChangeSetAction({ + actionName: 'PrepareChanges', + stackName: stackName, + changeSetName: changeSetName, + adminPermissions: true, + templatePath: artifact.atPath(testStage.stack.templateFile), + runOrder: 1, + }), + new cpactions.CloudFormationExecuteChangeSetAction({ + actionName: 'ExecuteChanges', + stackName: stackName, + changeSetName: changeSetName, + runOrder: 2, + }), +]; + +const testIStage = pipeline.addStage({ + stageName: testStage.stageName, + actions: stageActions, +}); +/** + * This imports the service so it can be used by the ECS Deploy Action. + * This must use the serviceArn so that the region is set correctly. + * The VPC doesn't actually matter and it doesn't get created. + * The construct ids will need to be unique. +*/ +const service = ecs.FargateService.fromFargateServiceAttributes(pipelineStack, 'FargateService', { + serviceArn: testStage.serviceArn, + cluster: ecs.Cluster.fromClusterAttributes(pipelineStack, 'Cluster', { + vpc: new ec2.Vpc(pipelineStack, 'Vpc'), + securityGroups: [], + clusterName: testStage.clusterName, + }), +}); +/** + * It is highly recommended passing in the role from the stage/stack, if not a new role + * will be added to the pipeline action, will not exist in the account you are deploying to. + * + * Using input however imageFile could be used as well, based on your use case. + */ +const deployAction = new cpactions.EcsDeployAction({ + actionName: 'ECS', + service: service, + input: artifact, + role: testStage.deployRole, + runOrder: 3, +}); +testIStage.addAction(deployAction); /// !hide diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.expected.json similarity index 69% rename from packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.expected.json index 26bce8e4522fa..b618317707932 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.expected.json @@ -28,7 +28,7 @@ { "Ref": "AWS::Partition" }, - ":iam::234567890123:root" + ":iam::pipeline-account:root" ] ] } @@ -45,7 +45,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-createpipelinestackpipelinecc1a70d2", + "AliasName": "alias/codepipeline-newecsservicepipelinestackpipeline99b88d5b", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", @@ -165,6 +165,26 @@ ] } }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineTestStagePrepareChangesCodePipelineActionRole96F79520", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineTestStageExecuteChangesCodePipelineActionRole92A91B69", + "Arn" + ] + } + }, { "Action": [ "s3:GetObject*", @@ -184,7 +204,7 @@ { "Ref": "AWS::Partition" }, - ":s3:::createpipelinestack-suppoeplicationbucket85400285e4a9a6dd80de" + ":s3:::newecsservicepipelinestaceplicationbucketaedc716a1d343700eab8" ] ] }, @@ -196,7 +216,7 @@ { "Ref": "AWS::Partition" }, - ":s3:::createpipelinestack-suppoeplicationbucket85400285e4a9a6dd80de/*" + ":s3:::newecsservicepipelinestaceplicationbucketaedc716a1d343700eab8/*" ] ] } @@ -224,7 +244,7 @@ { "Ref": "AWS::Partition" }, - ":iam::123456789012:role/deployrole" + ":iam::service-account:role/deployrole" ] ] } @@ -284,6 +304,61 @@ }, { "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "StackName": "TestStage-ecsStack", + "Capabilities": "CAPABILITY_NAMED_IAM", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineTestStagePrepareChangesRoleB43D8DC1", + "Arn" + ] + }, + "ActionMode": "CHANGE_SET_REPLACE", + "ChangeSetName": "changeset-TestStage-ecsStack", + "TemplatePath": "Artifact::NewEcsServicePipelineStackTestStageecsStackE57BE78B.template.json" + }, + "InputArtifacts": [ + { + "Name": "Artifact" + } + ], + "Name": "PrepareChanges", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineTestStagePrepareChangesCodePipelineActionRole96F79520", + "Arn" + ] + }, + "RunOrder": 1 + }, + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "StackName": "TestStage-ecsStack", + "ActionMode": "CHANGE_SET_EXECUTE", + "ChangeSetName": "changeset-TestStage-ecsStack" + }, + "Name": "ExecuteChanges", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineTestStageExecuteChangesCodePipelineActionRole92A91B69", + "Arn" + ] + }, + "RunOrder": 2 + }, { "ActionTypeId": { "Category": "Deploy", @@ -292,9 +367,8 @@ "Version": "1" }, "Configuration": { - "ClusterName": "teststage-ecsstackeecsstackclusterc0cbbdd5a4045b3c3e44", - "ServiceName": "teststage-ecsstackckfargateservicee0fd7ddb3c75ddfbf877", - "FileName": "imageFile.json" + "ClusterName": "teststage-ecsstackeecsstackcluster331c7d387248ac9d21ad", + "ServiceName": "teststage-ecsstackckfargateservicefb3c60b42c6b80ed8e7b" }, "InputArtifacts": [ { @@ -302,7 +376,7 @@ } ], "Name": "ECS", - "Region": "us-west-2", + "Region": "service-region", "RoleArn": { "Fn::Join": [ "", @@ -311,11 +385,11 @@ { "Ref": "AWS::Partition" }, - ":iam::123456789012:role/deployrole" + ":iam::service-account:role/deployrole" ] ] }, - "RunOrder": 1 + "RunOrder": 3 } ], "Name": "TestStage" @@ -333,16 +407,16 @@ { "Ref": "AWS::Partition" }, - ":kms:us-west-2:234567890123:alias/ck-suppotencryptionaliasc4e901c15e32d81a2380" + ":kms:service-region:pipeline-account:alias/linestactencryptionalias6398ac74d0eaa87e2093" ] ] }, "Type": "KMS" }, - "Location": "createpipelinestack-suppoeplicationbucket85400285e4a9a6dd80de", + "Location": "newecsservicepipelinestaceplicationbucketaedc716a1d343700eab8", "Type": "S3" }, - "Region": "us-west-2" + "Region": "service-region" }, { "ArtifactStore": { @@ -360,7 +434,7 @@ }, "Type": "S3" }, - "Region": "us-east-1" + "Region": "pipeline-region" } ] }, @@ -386,7 +460,7 @@ { "Ref": "AWS::Partition" }, - ":iam::234567890123:root" + ":iam::pipeline-account:root" ] ] } @@ -488,6 +562,279 @@ ] } }, + "PipelineTestStagePrepareChangesCodePipelineActionRole96F79520": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::pipeline-account:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineTestStagePrepareChangesCodePipelineActionRoleDefaultPolicy815C349E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineTestStagePrepareChangesRoleB43D8DC1", + "Arn" + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + { + "Action": [ + "cloudformation:CreateChangeSet", + "cloudformation:DeleteChangeSet", + "cloudformation:DescribeChangeSet", + "cloudformation:DescribeStacks" + ], + "Condition": { + "StringEqualsIfExists": { + "cloudformation:ChangeSetName": "changeset-TestStage-ecsStack" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":cloudformation:pipeline-region:pipeline-account:stack/TestStage-ecsStack/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineTestStagePrepareChangesCodePipelineActionRoleDefaultPolicy815C349E", + "Roles": [ + { + "Ref": "PipelineTestStagePrepareChangesCodePipelineActionRole96F79520" + } + ] + } + }, + "PipelineTestStagePrepareChangesRoleB43D8DC1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "cloudformation.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineTestStagePrepareChangesRoleDefaultPolicy56424C45": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + { + "Action": "*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineTestStagePrepareChangesRoleDefaultPolicy56424C45", + "Roles": [ + { + "Ref": "PipelineTestStagePrepareChangesRoleB43D8DC1" + } + ] + } + }, + "PipelineTestStageExecuteChangesCodePipelineActionRole92A91B69": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::pipeline-account:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineTestStageExecuteChangesCodePipelineActionRoleDefaultPolicy30A1554D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cloudformation:DescribeChangeSet", + "cloudformation:DescribeStacks", + "cloudformation:ExecuteChangeSet" + ], + "Condition": { + "StringEqualsIfExists": { + "cloudformation:ChangeSetName": "changeset-TestStage-ecsStack" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":cloudformation:pipeline-region:pipeline-account:stack/TestStage-ecsStack/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineTestStageExecuteChangesCodePipelineActionRoleDefaultPolicy30A1554D", + "Roles": [ + { + "Ref": "PipelineTestStageExecuteChangesCodePipelineActionRole92A91B69" + } + ] + } + }, "Vpc8378EB38": { "Type": "AWS::EC2::VPC", "Properties": { @@ -498,7 +845,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc" + "Value": "NewEcsServicePipelineStack/Vpc" } ] } @@ -523,7 +870,7 @@ }, { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet1" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet1" } ] } @@ -537,7 +884,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet1" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet1" } ] } @@ -575,7 +922,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet1" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet1" } ] } @@ -595,7 +942,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet1" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet1" } ] } @@ -620,7 +967,7 @@ }, { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet2" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet2" } ] } @@ -634,7 +981,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet2" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet2" } ] } @@ -672,7 +1019,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet2" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet2" } ] } @@ -692,7 +1039,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet2" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet2" } ] } @@ -717,7 +1064,7 @@ }, { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet3" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet3" } ] } @@ -731,7 +1078,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet3" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet3" } ] } @@ -769,7 +1116,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet3" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet3" } ] } @@ -789,7 +1136,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PublicSubnet3" + "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet3" } ] } @@ -814,7 +1161,7 @@ }, { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PrivateSubnet1" + "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet1" } ] } @@ -828,7 +1175,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PrivateSubnet1" + "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet1" } ] } @@ -876,7 +1223,7 @@ }, { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PrivateSubnet2" + "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet2" } ] } @@ -890,7 +1237,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PrivateSubnet2" + "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet2" } ] } @@ -938,7 +1285,7 @@ }, { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PrivateSubnet3" + "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet3" } ] } @@ -952,7 +1299,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc/PrivateSubnet3" + "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet3" } ] } @@ -986,7 +1333,7 @@ "Tags": [ { "Key": "Name", - "Value": "CreatePipelineStack/Vpc" + "Value": "NewEcsServicePipelineStack/Vpc" } ] } @@ -1023,7 +1370,7 @@ { "Ref": "AWS::Partition" }, - ":iam::234567890123:root" + ":iam::pipeline-account:root" ] ] } @@ -1048,7 +1395,7 @@ { "Ref": "AWS::Partition" }, - ":iam::234567890123:root" + ":iam::pipeline-account:root" ] ] } @@ -1070,7 +1417,7 @@ { "Ref": "AWS::Partition" }, - ":iam::123456789012:role/deployrole" + ":iam::service-account:role/deployrole" ] ] } @@ -1087,7 +1434,7 @@ "CrossRegionCodePipelineReplicationBucketEncryptionAliasF1A0F37D": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/ck-suppotencryptionaliasc4e901c15e32d81a2380", + "AliasName": "alias/linestactencryptionalias6398ac74d0eaa87e2093", "TargetKeyId": { "Fn::GetAtt": [ "CrossRegionCodePipelineReplicationBucketEncryptionKey70216490", @@ -1113,7 +1460,7 @@ { "Ref": "AWS::Partition" }, - ":kms:us-west-2:234567890123:alias/ck-suppotencryptionaliasc4e901c15e32d81a2380" + ":kms:service-region:pipeline-account:alias/linestactencryptionalias6398ac74d0eaa87e2093" ] ] }, @@ -1122,7 +1469,7 @@ } ] }, - "BucketName": "createpipelinestack-suppoeplicationbucket85400285e4a9a6dd80de", + "BucketName": "newecsservicepipelinestaceplicationbucketaedc716a1d343700eab8", "PublicAccessBlockConfiguration": { "BlockPublicAcls": true, "BlockPublicPolicy": true, @@ -1157,7 +1504,7 @@ { "Ref": "AWS::Partition" }, - ":iam::123456789012:role/deployrole" + ":iam::service-account:role/deployrole" ] ] } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.ts similarity index 61% rename from packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.ts index 19fd7346bbb83..e3d3facc96f32 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-create-cross-account.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.ts @@ -4,17 +4,16 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; -import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as cpactions from '../lib'; - +import { EcsServiceCrossRegionAccountPipelineStack } from './ecs-pipeline-cross-region-account-helpers'; /** * This example demonstrates how to create a CodePipeline that deploys to a new ECS Service across * accounts and regions. * This will not deploy because integ tests only run in one account. - * Updates to this require yarn integ --dry-run integ.pipeline-ecs-create-cross-account.lit.js to generate the expected JSON file. + * Updates to this require yarn integ --dry-run integ.ecs-pipeline-cross-region-account-new.lit.js to generate the expected JSON file. */ /// !show @@ -24,14 +23,14 @@ const app = new cdk.App(); /** * This is the Stack which will create an ECS Service and gets deployed to. */ - -class TestCreateEcsStack extends cdk.Stack { +class NewEcsServiceStack extends cdk.Stack { public readonly serviceArn: string; public readonly clusterName: string; public readonly deployRole: iam.IRole; constructor(scope: Construct, id: string, props: cdk.StackProps) { super(scope, id, props); + const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition'); taskDefinition.addContainer('MainContainer', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), @@ -114,21 +113,23 @@ class TestCreateEcsStack extends cdk.Stack { }, }, })); - } } /** - * This is the Stage which does our create using {@link TestCreateEcsStack}. + * This is the Stage which does our create using {@link NewEcsServiceStack}. */ -class TestCreateEcsStage extends cdk.Stage { +class NewEcsServiceStage extends cdk.Stage { public readonly serviceArn: string; public readonly clusterName: string; public readonly deployRole: iam.IRole; + public readonly stack: cdk.Stack; constructor(scope: Construct, id: string, props: cdk.StageProps) { super(scope, id, props); - const testStack = new TestCreateEcsStack(this, 'ecsStack', {}); + + const testStack = new NewEcsServiceStack(this, 'ecsStack', {}); + this.stack = testStack; this.serviceArn = testStack.serviceArn; this.clusterName = testStack.clusterName; this.deployRole = testStack.deployRole; @@ -137,72 +138,75 @@ class TestCreateEcsStage extends cdk.Stage { /** * This is our pipeline which will create an ECS Service and deploy - * to is using {@link EcsDeployAction} using our {@link TestCreateEcsStage} + * to is using {@link EcsDeployAction} using our {@link NewEcsServiceStage} */ -class PipelineEcsDeployCreateStack extends cdk.Stack { - constructor(scope: Construct, id: string, props: cdk.StackProps) { - super(scope, id, props); - const artifact = new codepipeline.Artifact('Artifact'); - const bucket = new s3.Bucket(this, 'PipelineBucket', { - versioned: true, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - const source = new cpactions.S3SourceAction({ - actionName: 'Source', - output: artifact, - bucket, - bucketKey: 'key', - }); - const pipeline = new codepipeline.Pipeline(this, 'Pipeline', { - stages: [ - { - stageName: 'Source', - actions: [source], - }, - ], - }); - const testStage = new TestCreateEcsStage(this, 'TestStage', { - env: { - region: 'us-west-2', - account: '123456789012', - }, - }); - const testIStage = pipeline.addStage(testStage); - /** - * This imports the service so it can be used by the ECS Deploy Action. - * This must use the serviceArn so that the region is set correctly. - * The VPC doesn't actually matter and it doesn't get created. - * The construct ids will need to be unique. - */ - const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { - serviceArn: testStage.serviceArn, - cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { - vpc: new ec2.Vpc(this, 'Vpc'), - securityGroups: [], - clusterName: testStage.clusterName, - }), - }); - /** - * It is highly recommended passing in the role from the stage/stack, if not a new role - * will be added to the pipeline action, will not exist in the account you are deploying to. - */ - const deployAction = new cpactions.EcsDeployAction({ - actionName: 'ECS', - service: service, - imageFile: artifact.atPath('imageFile.json'), - role: testStage.deployRole, - }); - testIStage.addAction(deployAction); - } -} +const pipelineStack = new EcsServiceCrossRegionAccountPipelineStack(app, 'NewEcsServicePipelineStack', { + env: { + region: 'pipeline-region', + account: 'pipeline-account', + }, +}); +const pipeline: codepipeline.Pipeline = pipelineStack.pipeline; +const artifact: codepipeline.Artifact = pipelineStack.artifact; -// Define our ECS Deploy Crate Stack related to import and deploy existing services. -new PipelineEcsDeployCreateStack(app, 'CreatePipelineStack', { +const testStage = new NewEcsServiceStage(pipelineStack, 'TestStage', { env: { - region: 'us-east-1', - account: '234567890123', + region: 'service-region', + account: 'service-account', }, }); +const stackName = testStage.stack.stackName; +const changeSetName = `changeset-${testStage.stack.stackName}`; +const stageActions = [ + new cpactions.CloudFormationCreateReplaceChangeSetAction({ + actionName: 'PrepareChanges', + stackName: stackName, + changeSetName: changeSetName, + adminPermissions: true, + templatePath: artifact.atPath(testStage.stack.templateFile), + runOrder: 1, + }), + new cpactions.CloudFormationExecuteChangeSetAction({ + actionName: 'ExecuteChanges', + stackName: stackName, + changeSetName: changeSetName, + runOrder: 2, + }), +]; + +const testIStage = pipeline.addStage({ + stageName: testStage.stageName, + actions: stageActions, +}); + +/** + * This imports the service so it can be used by the ECS Deploy Action. + * This must use the serviceArn so that the region is set correctly. + * The VPC doesn't actually matter and it doesn't get created. + * The construct ids will need to be unique. + */ +const service = ecs.FargateService.fromFargateServiceAttributes(pipelineStack, 'FargateService', { + serviceArn: testStage.serviceArn, + cluster: ecs.Cluster.fromClusterAttributes(pipelineStack, 'Cluster', { + vpc: new ec2.Vpc(pipelineStack, 'Vpc'), + securityGroups: [], + clusterName: testStage.clusterName, + }), +}); +/** + * It is highly recommended passing in the role from the stage/stack, if not a new role + * will be added to the pipeline action, will not exist in the account you are deploying to. + * + * Using input however imageFile could be used as well, based on your use case. + */ +const deployAction = new cpactions.EcsDeployAction({ + actionName: 'ECS', + service: service, + input: artifact, + role: testStage.deployRole, + runOrder: 3, +}); +testIStage.addAction(deployAction); /// !hide From 694e07f1223b0c5816946f5b6ab211fd8459c42e Mon Sep 17 00:00:00 2001 From: Toby Tipton Date: Mon, 24 Jan 2022 09:06:38 -0500 Subject: [PATCH 5/5] update tests with BaseService.fromServiceArnWithCluster --- ...-region-account-existing.lit.expected.json | 595 +++--------------- ...eline-cross-region-account-existing.lit.ts | 65 +- ...cross-region-account-new.lit.expected.json | 595 +++--------------- ...s-pipeline-cross-region-account-new.lit.ts | 47 +- 4 files changed, 211 insertions(+), 1091 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.expected.json index 0bdf5188496c5..93bb93fe5a608 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.expected.json @@ -84,6 +84,53 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, + "PipelineArtifactsBucketPolicyD4F9712A": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, "PipelineRoleD68726F7": { "Type": "AWS::IAM::Role", "Properties": { @@ -834,520 +881,6 @@ } ] } - }, - "Vpc8378EB38": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16", - "EnableDnsHostnames": true, - "EnableDnsSupport": true, - "InstanceTenancy": "default", - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc" - } - ] - } - }, - "VpcPublicSubnet1Subnet5C2D37C4": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.0.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1a", - "MapPublicIpOnLaunch": true, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Public" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Public" - }, - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet1" - } - ] - } - }, - "VpcPublicSubnet1RouteTable6C95E38E": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet1" - } - ] - } - }, - "VpcPublicSubnet1RouteTableAssociation97140677": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet1RouteTable6C95E38E" - }, - "SubnetId": { - "Ref": "VpcPublicSubnet1Subnet5C2D37C4" - } - } - }, - "VpcPublicSubnet1DefaultRoute3DA9E72A": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet1RouteTable6C95E38E" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VpcIGWD7BA715C" - } - }, - "DependsOn": [ - "VpcVPCGWBF912B6E" - ] - }, - "VpcPublicSubnet1EIPD7E02669": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet1" - } - ] - } - }, - "VpcPublicSubnet1NATGateway4D7517AA": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "SubnetId": { - "Ref": "VpcPublicSubnet1Subnet5C2D37C4" - }, - "AllocationId": { - "Fn::GetAtt": [ - "VpcPublicSubnet1EIPD7E02669", - "AllocationId" - ] - }, - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet1" - } - ] - } - }, - "VpcPublicSubnet2Subnet691E08A3": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.32.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1b", - "MapPublicIpOnLaunch": true, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Public" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Public" - }, - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet2" - } - ] - } - }, - "VpcPublicSubnet2RouteTable94F7E489": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet2" - } - ] - } - }, - "VpcPublicSubnet2RouteTableAssociationDD5762D8": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet2RouteTable94F7E489" - }, - "SubnetId": { - "Ref": "VpcPublicSubnet2Subnet691E08A3" - } - } - }, - "VpcPublicSubnet2DefaultRoute97F91067": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet2RouteTable94F7E489" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VpcIGWD7BA715C" - } - }, - "DependsOn": [ - "VpcVPCGWBF912B6E" - ] - }, - "VpcPublicSubnet2EIP3C605A87": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet2" - } - ] - } - }, - "VpcPublicSubnet2NATGateway9182C01D": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "SubnetId": { - "Ref": "VpcPublicSubnet2Subnet691E08A3" - }, - "AllocationId": { - "Fn::GetAtt": [ - "VpcPublicSubnet2EIP3C605A87", - "AllocationId" - ] - }, - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet2" - } - ] - } - }, - "VpcPublicSubnet3SubnetBE12F0B6": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.64.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1c", - "MapPublicIpOnLaunch": true, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Public" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Public" - }, - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet3" - } - ] - } - }, - "VpcPublicSubnet3RouteTable93458DBB": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet3" - } - ] - } - }, - "VpcPublicSubnet3RouteTableAssociation1F1EDF02": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet3RouteTable93458DBB" - }, - "SubnetId": { - "Ref": "VpcPublicSubnet3SubnetBE12F0B6" - } - } - }, - "VpcPublicSubnet3DefaultRoute4697774F": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet3RouteTable93458DBB" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VpcIGWD7BA715C" - } - }, - "DependsOn": [ - "VpcVPCGWBF912B6E" - ] - }, - "VpcPublicSubnet3EIP3A666A23": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet3" - } - ] - } - }, - "VpcPublicSubnet3NATGateway7640CD1D": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "SubnetId": { - "Ref": "VpcPublicSubnet3SubnetBE12F0B6" - }, - "AllocationId": { - "Fn::GetAtt": [ - "VpcPublicSubnet3EIP3A666A23", - "AllocationId" - ] - }, - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PublicSubnet3" - } - ] - } - }, - "VpcPrivateSubnet1Subnet536B997A": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.96.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1a", - "MapPublicIpOnLaunch": false, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Private" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Private" - }, - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet1" - } - ] - } - }, - "VpcPrivateSubnet1RouteTableB2C5B500": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet1" - } - ] - } - }, - "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" - }, - "SubnetId": { - "Ref": "VpcPrivateSubnet1Subnet536B997A" - } - } - }, - "VpcPrivateSubnet1DefaultRouteBE02A9ED": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VpcPublicSubnet1NATGateway4D7517AA" - } - } - }, - "VpcPrivateSubnet2Subnet3788AAA1": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.128.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1b", - "MapPublicIpOnLaunch": false, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Private" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Private" - }, - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet2" - } - ] - } - }, - "VpcPrivateSubnet2RouteTableA678073B": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet2" - } - ] - } - }, - "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet2RouteTableA678073B" - }, - "SubnetId": { - "Ref": "VpcPrivateSubnet2Subnet3788AAA1" - } - } - }, - "VpcPrivateSubnet2DefaultRoute060D2087": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet2RouteTableA678073B" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VpcPublicSubnet2NATGateway9182C01D" - } - } - }, - "VpcPrivateSubnet3SubnetF258B56E": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.160.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1c", - "MapPublicIpOnLaunch": false, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Private" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Private" - }, - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet3" - } - ] - } - }, - "VpcPrivateSubnet3RouteTableD98824C7": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc/PrivateSubnet3" - } - ] - } - }, - "VpcPrivateSubnet3RouteTableAssociation16BDDC43": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet3RouteTableD98824C7" - }, - "SubnetId": { - "Ref": "VpcPrivateSubnet3SubnetF258B56E" - } - } - }, - "VpcPrivateSubnet3DefaultRoute94B74F0D": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet3RouteTableD98824C7" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VpcPublicSubnet3NATGateway7640CD1D" - } - } - }, - "VpcIGWD7BA715C": { - "Type": "AWS::EC2::InternetGateway", - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "ExistingEcsServicePipelineStack/Vpc" - } - ] - } - }, - "VpcVPCGWBF912B6E": { - "Type": "AWS::EC2::VPCGatewayAttachment", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "InternetGatewayId": { - "Ref": "VpcIGWD7BA715C" - } - } } } }, @@ -1488,6 +1021,40 @@ }, "PolicyDocument": { "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "CrossRegionCodePipelineReplicationBucketFC3227F2", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CrossRegionCodePipelineReplicationBucketFC3227F2", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, { "Action": [ "s3:GetObject*", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.ts index da1b80792b0e6..f72d164780219 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-existing.lit.ts @@ -29,7 +29,6 @@ const app = new cdk.App(); */ class ExistingEcsServiceStack extends cdk.Stack { public readonly serviceArn: string; - public readonly clusterName: string; public readonly deployRole: iam.IRole; constructor(scope: Construct, id: string, props: cdk.StackProps) { @@ -39,16 +38,24 @@ class ExistingEcsServiceStack extends cdk.Stack { const vpc = ec2.Vpc.fromLookup(this, 'VpcLookup', { isDefault: false, }); - this.clusterName = 'cluster-name'; + const clusterName = 'cluster-name'; + const cluster = ecs.Cluster.fromClusterAttributes(this, 'Cluster', { + vpc, + securityGroups: [], + clusterName: clusterName, + }); const service = ecs.FargateService.fromFargateServiceAttributes(this, 'FargateService', { serviceName: 'service-name', - cluster: ecs.Cluster.fromClusterAttributes(this, 'Cluster', { - vpc, - securityGroups: [], - clusterName: this.clusterName, - }), + cluster: cluster, + }); + /** + * The default serviceArn doesn't include the cluster, as it isn't in the new format. + */ + this.serviceArn = this.formatArn({ + service: 'ecs', + resource: 'service', + resourceName: `${cluster.clusterName}/${service.serviceName}`, }); - this.serviceArn = service.serviceArn; /** * The deployRole is being looked up from an existing role. * If you want to create a new role here you can do that however you will need this stack deployed @@ -104,7 +111,6 @@ class ExistingEcsServiceStack extends cdk.Stack { */ class ExistingEcsServiceStage extends cdk.Stage { public readonly serviceArn: string; - public readonly clusterName: string; public readonly deployRole: iam.IRole; public readonly stack: cdk.Stack @@ -114,7 +120,6 @@ class ExistingEcsServiceStage extends cdk.Stage { const testStack = new ExistingEcsServiceStack(this, 'ecsStack', {}); this.stack = testStack; this.serviceArn = testStack.serviceArn; - this.clusterName = testStack.clusterName; this.deployRole = testStack.deployRole; } } @@ -140,22 +145,21 @@ const testStage = new ExistingEcsServiceStage(pipelineStack, 'TestStage', { }); const stackName = testStage.stack.stackName; const changeSetName = `changeset-${testStage.stack.stackName}`; -const stageActions = [ - new cpactions.CloudFormationCreateReplaceChangeSetAction({ - actionName: 'PrepareChanges', - stackName: stackName, - changeSetName: changeSetName, - adminPermissions: true, - templatePath: artifact.atPath(testStage.stack.templateFile), - runOrder: 1, - }), - new cpactions.CloudFormationExecuteChangeSetAction({ - actionName: 'ExecuteChanges', - stackName: stackName, - changeSetName: changeSetName, - runOrder: 2, - }), -]; +const stageActions = []; +stageActions.push(new cpactions.CloudFormationCreateReplaceChangeSetAction({ + actionName: 'PrepareChanges', + stackName: stackName, + changeSetName: changeSetName, + adminPermissions: true, + templatePath: artifact.atPath(testStage.stack.templateFile), + runOrder: 1, +})); +stageActions.push(new cpactions.CloudFormationExecuteChangeSetAction({ + actionName: 'ExecuteChanges', + stackName: stackName, + changeSetName: changeSetName, + runOrder: 2, +})); const testIStage = pipeline.addStage({ stageName: testStage.stageName, @@ -167,14 +171,7 @@ const testIStage = pipeline.addStage({ * The VPC doesn't actually matter and it doesn't get created. * The construct ids will need to be unique. */ -const service = ecs.FargateService.fromFargateServiceAttributes(pipelineStack, 'FargateService', { - serviceArn: testStage.serviceArn, - cluster: ecs.Cluster.fromClusterAttributes(pipelineStack, 'Cluster', { - vpc: new ec2.Vpc(pipelineStack, 'Vpc'), - securityGroups: [], - clusterName: testStage.clusterName, - }), -}); +const service = ecs.BaseService.fromServiceArnWithCluster(pipelineStack, 'FargateService', testStage.serviceArn); /** * It is highly recommended passing in the role from the stage/stack, if not a new role * will be added to the pipeline action, will not exist in the account you are deploying to. diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.expected.json index b618317707932..f763680b44113 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.expected.json @@ -84,6 +84,53 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, + "PipelineArtifactsBucketPolicyD4F9712A": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, "PipelineRoleD68726F7": { "Type": "AWS::IAM::Role", "Properties": { @@ -834,520 +881,6 @@ } ] } - }, - "Vpc8378EB38": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16", - "EnableDnsHostnames": true, - "EnableDnsSupport": true, - "InstanceTenancy": "default", - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc" - } - ] - } - }, - "VpcPublicSubnet1Subnet5C2D37C4": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.0.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1a", - "MapPublicIpOnLaunch": true, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Public" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Public" - }, - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet1" - } - ] - } - }, - "VpcPublicSubnet1RouteTable6C95E38E": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet1" - } - ] - } - }, - "VpcPublicSubnet1RouteTableAssociation97140677": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet1RouteTable6C95E38E" - }, - "SubnetId": { - "Ref": "VpcPublicSubnet1Subnet5C2D37C4" - } - } - }, - "VpcPublicSubnet1DefaultRoute3DA9E72A": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet1RouteTable6C95E38E" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VpcIGWD7BA715C" - } - }, - "DependsOn": [ - "VpcVPCGWBF912B6E" - ] - }, - "VpcPublicSubnet1EIPD7E02669": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet1" - } - ] - } - }, - "VpcPublicSubnet1NATGateway4D7517AA": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "SubnetId": { - "Ref": "VpcPublicSubnet1Subnet5C2D37C4" - }, - "AllocationId": { - "Fn::GetAtt": [ - "VpcPublicSubnet1EIPD7E02669", - "AllocationId" - ] - }, - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet1" - } - ] - } - }, - "VpcPublicSubnet2Subnet691E08A3": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.32.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1b", - "MapPublicIpOnLaunch": true, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Public" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Public" - }, - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet2" - } - ] - } - }, - "VpcPublicSubnet2RouteTable94F7E489": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet2" - } - ] - } - }, - "VpcPublicSubnet2RouteTableAssociationDD5762D8": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet2RouteTable94F7E489" - }, - "SubnetId": { - "Ref": "VpcPublicSubnet2Subnet691E08A3" - } - } - }, - "VpcPublicSubnet2DefaultRoute97F91067": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet2RouteTable94F7E489" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VpcIGWD7BA715C" - } - }, - "DependsOn": [ - "VpcVPCGWBF912B6E" - ] - }, - "VpcPublicSubnet2EIP3C605A87": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet2" - } - ] - } - }, - "VpcPublicSubnet2NATGateway9182C01D": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "SubnetId": { - "Ref": "VpcPublicSubnet2Subnet691E08A3" - }, - "AllocationId": { - "Fn::GetAtt": [ - "VpcPublicSubnet2EIP3C605A87", - "AllocationId" - ] - }, - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet2" - } - ] - } - }, - "VpcPublicSubnet3SubnetBE12F0B6": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.64.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1c", - "MapPublicIpOnLaunch": true, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Public" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Public" - }, - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet3" - } - ] - } - }, - "VpcPublicSubnet3RouteTable93458DBB": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet3" - } - ] - } - }, - "VpcPublicSubnet3RouteTableAssociation1F1EDF02": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet3RouteTable93458DBB" - }, - "SubnetId": { - "Ref": "VpcPublicSubnet3SubnetBE12F0B6" - } - } - }, - "VpcPublicSubnet3DefaultRoute4697774F": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPublicSubnet3RouteTable93458DBB" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VpcIGWD7BA715C" - } - }, - "DependsOn": [ - "VpcVPCGWBF912B6E" - ] - }, - "VpcPublicSubnet3EIP3A666A23": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet3" - } - ] - } - }, - "VpcPublicSubnet3NATGateway7640CD1D": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "SubnetId": { - "Ref": "VpcPublicSubnet3SubnetBE12F0B6" - }, - "AllocationId": { - "Fn::GetAtt": [ - "VpcPublicSubnet3EIP3A666A23", - "AllocationId" - ] - }, - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PublicSubnet3" - } - ] - } - }, - "VpcPrivateSubnet1Subnet536B997A": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.96.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1a", - "MapPublicIpOnLaunch": false, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Private" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Private" - }, - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet1" - } - ] - } - }, - "VpcPrivateSubnet1RouteTableB2C5B500": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet1" - } - ] - } - }, - "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" - }, - "SubnetId": { - "Ref": "VpcPrivateSubnet1Subnet536B997A" - } - } - }, - "VpcPrivateSubnet1DefaultRouteBE02A9ED": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VpcPublicSubnet1NATGateway4D7517AA" - } - } - }, - "VpcPrivateSubnet2Subnet3788AAA1": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.128.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1b", - "MapPublicIpOnLaunch": false, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Private" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Private" - }, - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet2" - } - ] - } - }, - "VpcPrivateSubnet2RouteTableA678073B": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet2" - } - ] - } - }, - "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet2RouteTableA678073B" - }, - "SubnetId": { - "Ref": "VpcPrivateSubnet2Subnet3788AAA1" - } - } - }, - "VpcPrivateSubnet2DefaultRoute060D2087": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet2RouteTableA678073B" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VpcPublicSubnet2NATGateway9182C01D" - } - } - }, - "VpcPrivateSubnet3SubnetF258B56E": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "CidrBlock": "10.0.160.0/19", - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "AvailabilityZone": "dummy1c", - "MapPublicIpOnLaunch": false, - "Tags": [ - { - "Key": "aws-cdk:subnet-name", - "Value": "Private" - }, - { - "Key": "aws-cdk:subnet-type", - "Value": "Private" - }, - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet3" - } - ] - } - }, - "VpcPrivateSubnet3RouteTableD98824C7": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc/PrivateSubnet3" - } - ] - } - }, - "VpcPrivateSubnet3RouteTableAssociation16BDDC43": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet3RouteTableD98824C7" - }, - "SubnetId": { - "Ref": "VpcPrivateSubnet3SubnetF258B56E" - } - } - }, - "VpcPrivateSubnet3DefaultRoute94B74F0D": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VpcPrivateSubnet3RouteTableD98824C7" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VpcPublicSubnet3NATGateway7640CD1D" - } - } - }, - "VpcIGWD7BA715C": { - "Type": "AWS::EC2::InternetGateway", - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "NewEcsServicePipelineStack/Vpc" - } - ] - } - }, - "VpcVPCGWBF912B6E": { - "Type": "AWS::EC2::VPCGatewayAttachment", - "Properties": { - "VpcId": { - "Ref": "Vpc8378EB38" - }, - "InternetGatewayId": { - "Ref": "VpcIGWD7BA715C" - } - } } } }, @@ -1488,6 +1021,40 @@ }, "PolicyDocument": { "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "CrossRegionCodePipelineReplicationBucketFC3227F2", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CrossRegionCodePipelineReplicationBucketFC3227F2", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, { "Action": [ "s3:GetObject*", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.ts index e3d3facc96f32..44c1fa15b0d05 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.ecs-pipeline-cross-region-account-new.lit.ts @@ -25,7 +25,6 @@ const app = new cdk.App(); */ class NewEcsServiceStack extends cdk.Stack { public readonly serviceArn: string; - public readonly clusterName: string; public readonly deployRole: iam.IRole; constructor(scope: Construct, id: string, props: cdk.StackProps) { @@ -59,13 +58,13 @@ class NewEcsServiceStack extends cdk.Stack { * The token causes in the ECS Deploy Action to not be able to determine region. * Using the formatArn produces a ARN which only makes the resourceName as a token, which allows * the ECS Deploy Action to determine the region and account you are deploying to. + * https://github.com/aws/aws-cdk/pull/18382 addresses the serviceArn not being a token. */ this.serviceArn = this.formatArn({ service: 'ecs', resource: 'service', - resourceName: service.serviceName, + resourceName: `${cluster.clusterName}/${service.serviceName}`, }); - this.clusterName = cluster.clusterName; /** * The deployRole is being looked up from an existing role. * If you want to create a new role here you can do that however you will need this stack deployed @@ -121,7 +120,6 @@ class NewEcsServiceStack extends cdk.Stack { */ class NewEcsServiceStage extends cdk.Stage { public readonly serviceArn: string; - public readonly clusterName: string; public readonly deployRole: iam.IRole; public readonly stack: cdk.Stack; @@ -131,7 +129,6 @@ class NewEcsServiceStage extends cdk.Stage { const testStack = new NewEcsServiceStack(this, 'ecsStack', {}); this.stack = testStack; this.serviceArn = testStack.serviceArn; - this.clusterName = testStack.clusterName; this.deployRole = testStack.deployRole; } } @@ -157,22 +154,21 @@ const testStage = new NewEcsServiceStage(pipelineStack, 'TestStage', { }); const stackName = testStage.stack.stackName; const changeSetName = `changeset-${testStage.stack.stackName}`; -const stageActions = [ - new cpactions.CloudFormationCreateReplaceChangeSetAction({ - actionName: 'PrepareChanges', - stackName: stackName, - changeSetName: changeSetName, - adminPermissions: true, - templatePath: artifact.atPath(testStage.stack.templateFile), - runOrder: 1, - }), - new cpactions.CloudFormationExecuteChangeSetAction({ - actionName: 'ExecuteChanges', - stackName: stackName, - changeSetName: changeSetName, - runOrder: 2, - }), -]; +const stageActions = []; +stageActions.push(new cpactions.CloudFormationCreateReplaceChangeSetAction({ + actionName: 'PrepareChanges', + stackName: stackName, + changeSetName: changeSetName, + adminPermissions: true, + templatePath: artifact.atPath(testStage.stack.templateFile), + runOrder: 1, +})); +stageActions.push(new cpactions.CloudFormationExecuteChangeSetAction({ + actionName: 'ExecuteChanges', + stackName: stackName, + changeSetName: changeSetName, + runOrder: 2, +})); const testIStage = pipeline.addStage({ stageName: testStage.stageName, @@ -185,14 +181,7 @@ const testIStage = pipeline.addStage({ * The VPC doesn't actually matter and it doesn't get created. * The construct ids will need to be unique. */ -const service = ecs.FargateService.fromFargateServiceAttributes(pipelineStack, 'FargateService', { - serviceArn: testStage.serviceArn, - cluster: ecs.Cluster.fromClusterAttributes(pipelineStack, 'Cluster', { - vpc: new ec2.Vpc(pipelineStack, 'Vpc'), - securityGroups: [], - clusterName: testStage.clusterName, - }), -}); +const service = ecs.BaseService.fromServiceArnWithCluster(pipelineStack, 'FargateService', testStage.serviceArn); /** * It is highly recommended passing in the role from the stage/stack, if not a new role * will be added to the pipeline action, will not exist in the account you are deploying to.