Skip to content

Commit 04252d7

Browse files
committed
feat(codepipeline): support cross-environment deployments for all actions
Previously, we only supported cross-environment deployments for CodeBuild and CloudFormation CodePipeline actions. This change adds this capability to all remaining AWS-owned actions. Fixes #3389
1 parent 923055e commit 04252d7

24 files changed

+240
-54
lines changed

packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2');
55
import iam = require('@aws-cdk/aws-iam');
66
import sns = require('@aws-cdk/aws-sns');
77

8-
import { CfnAutoScalingRollingUpdate, Construct, Duration, Fn, IResource, Lazy, Resource, Stack, Tag, Token, withResolved } from '@aws-cdk/core';
8+
import {
9+
CfnAutoScalingRollingUpdate, Construct, Duration, Fn, IResource, Lazy, PhysicalName, Resource, Stack,
10+
Tag, Token, withResolved
11+
} from '@aws-cdk/core';
912
import { CfnAutoScalingGroup, CfnAutoScalingGroupProps, CfnLaunchConfiguration } from './autoscaling.generated';
1013
import { BasicLifecycleHookProps, LifecycleHook } from './lifecycle-hook';
1114
import { BasicScheduledActionProps, ScheduledAction } from './scheduled-action';
@@ -414,6 +417,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
414417
this.node.applyAspect(new Tag(NAME_TAG, this.node.path));
415418

416419
this.role = props.role || new iam.Role(this, 'InstanceRole', {
420+
roleName: PhysicalName.GENERATE_IF_NEEDED,
417421
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com')
418422
});
419423

@@ -1091,4 +1095,4 @@ function stringifyNumber(x: number) {
10911095
} else {
10921096
return `${x}`;
10931097
}
1094-
}
1098+
}

packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export class CodeCommitSourceAction extends Action {
6868

6969
super({
7070
...props,
71+
resource: props.repository,
7172
category: codepipeline.ActionCategory.SOURCE,
7273
provider: 'CodeCommit',
7374
artifactBounds: sourceArtifactBounds(),

packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class CodeDeployServerDeployAction extends Action {
2626
constructor(props: CodeDeployServerDeployActionProps) {
2727
super({
2828
...props,
29+
resource: props.deploymentGroup,
2930
category: codepipeline.ActionCategory.DEPLOY,
3031
provider: 'CodeDeploy',
3132
artifactBounds: deployArtifactBounds(),

packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export class EcrSourceAction extends Action {
4141
constructor(props: EcrSourceActionProps) {
4242
super({
4343
...props,
44+
resource: props.repository,
4445
category: codepipeline.ActionCategory.SOURCE,
4546
provider: 'ECR',
4647
artifactBounds: sourceArtifactBounds(),

packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export class LambdaInvokeAction extends Action {
6060
constructor(props: LambdaInvokeActionProps) {
6161
super({
6262
...props,
63+
resource: props.lambda,
6364
category: codepipeline.ActionCategory.INVOKE,
6465
provider: 'Lambda',
6566
artifactBounds: {

packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export class S3DeployAction extends Action {
4040
constructor(props: S3DeployActionProps) {
4141
super({
4242
...props,
43+
resource: props.bucket,
4344
category: codepipeline.ActionCategory.DEPLOY,
4445
provider: 'S3',
4546
artifactBounds: deployArtifactBounds(),

packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export class S3SourceAction extends Action {
7474
constructor(props: S3SourceActionProps) {
7575
super({
7676
...props,
77+
resource: props.bucket,
7778
category: codepipeline.ActionCategory.SOURCE,
7879
provider: 'S3',
7980
artifactBounds: sourceArtifactBounds(),

packages/@aws-cdk/aws-codepipeline-actions/test/integ.cfn-template-from-repo.lit.expected.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,10 @@
393393
"ArtifactStore": {
394394
"EncryptionKey": {
395395
"Id": {
396-
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
396+
"Fn::GetAtt": [
397+
"PipelineArtifactsBucketEncryptionKey01D58D69",
398+
"Arn"
399+
]
397400
},
398401
"Type": "KMS"
399402
},

packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.expected.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,10 @@
518518
"ArtifactStore": {
519519
"EncryptionKey": {
520520
"Id": {
521-
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
521+
"Fn::GetAtt": [
522+
"PipelineArtifactsBucketEncryptionKey01D58D69",
523+
"Arn"
524+
]
522525
},
523526
"Type": "KMS"
524527
},

packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.expected.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,10 @@
296296
"ArtifactStore": {
297297
"EncryptionKey": {
298298
"Id": {
299-
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
299+
"Fn::GetAtt": [
300+
"PipelineArtifactsBucketEncryptionKey01D58D69",
301+
"Arn"
302+
]
300303
},
301304
"Type": "KMS"
302305
},

packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-alexa-deploy.expected.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,10 @@
295295
"ArtifactStore": {
296296
"EncryptionKey": {
297297
"Id": {
298-
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
298+
"Fn::GetAtt": [
299+
"PipelineArtifactsBucketEncryptionKey01D58D69",
300+
"Arn"
301+
]
299302
},
300303
"Type": "KMS"
301304
},

packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn.expected.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,10 @@
381381
"ArtifactStore": {
382382
"EncryptionKey": {
383383
"Id": {
384-
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
384+
"Fn::GetAtt": [
385+
"PipelineArtifactsBucketEncryptionKey01D58D69",
386+
"Arn"
387+
]
385388
},
386389
"Type": "KMS"
387390
},

packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.expected.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,10 @@
593593
"ArtifactStore": {
594594
"EncryptionKey": {
595595
"Id": {
596-
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
596+
"Fn::GetAtt": [
597+
"PipelineArtifactsBucketEncryptionKey01D58D69",
598+
"Arn"
599+
]
597600
},
598601
"Type": "KMS"
599602
},

packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit.expected.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,10 @@
367367
"ArtifactStore": {
368368
"EncryptionKey": {
369369
"Id": {
370-
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
370+
"Fn::GetAtt": [
371+
"PipelineArtifactsBucketEncryptionKey01D58D69",
372+
"Arn"
373+
]
371374
},
372375
"Type": "KMS"
373376
},

packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.expected.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,10 @@
330330
"ArtifactStore": {
331331
"EncryptionKey": {
332332
"Id": {
333-
"Ref": "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3"
333+
"Fn::GetAtt": [
334+
"MyPipelineArtifactsBucketEncryptionKey8BF0A7F3",
335+
"Arn"
336+
]
334337
},
335338
"Type": "KMS"
336339
},

packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.expected.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,10 @@
332332
"ArtifactStore": {
333333
"EncryptionKey": {
334334
"Id": {
335-
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
335+
"Fn::GetAtt": [
336+
"PipelineArtifactsBucketEncryptionKey01D58D69",
337+
"Arn"
338+
]
336339
},
337340
"Type": "KMS"
338341
},

packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,18 @@ export = {
789789
"Type": "S3",
790790
"Location": "replicationstackeplicationbucket2464cd5c33b386483b66",
791791
"EncryptionKey": {
792-
"Id": "alias/replicationstacktencryptionalias043cb2f8ceac9da9c07c",
792+
"Id": {
793+
"Fn::Join": [
794+
"",
795+
[
796+
"arn:",
797+
{
798+
"Ref": "AWS::Partition",
799+
},
800+
":kms:us-west-1:123456789012:alias/ionstacktencryptionalias043cb2f8ceac9da9c07c",
801+
],
802+
],
803+
},
793804
"Type": "KMS"
794805
},
795806
},

packages/@aws-cdk/aws-codepipeline/lib/cross-region-support-stack.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,40 @@ import kms = require('@aws-cdk/aws-kms');
22
import s3 = require('@aws-cdk/aws-s3');
33
import cdk = require('@aws-cdk/core');
44

5+
const REQUIRED_ALIAS_PREFIX = 'alias/';
6+
7+
/**
8+
* A class needed to work around CodePipeline's extremely small (100 characters)
9+
* limit for the name/ARN of the key in the ArtifactStore.
10+
* Limits the length of the alias' auto-generated name to 50 characters.
11+
*/
12+
class AliasWithShorterGeneratedName extends kms.Alias {
13+
protected generatePhysicalName(): string {
14+
let baseName = super.generatePhysicalName();
15+
if (baseName.startsWith(REQUIRED_ALIAS_PREFIX)) {
16+
// remove the prefix, because we're taking the last characters of the name below
17+
baseName = baseName.substring(REQUIRED_ALIAS_PREFIX.length);
18+
}
19+
const maxLength = 50 - REQUIRED_ALIAS_PREFIX.length;
20+
// take the last characters, as they include the hash,
21+
// and so have a higher chance of not colliding
22+
return REQUIRED_ALIAS_PREFIX + lastNCharacters(baseName, maxLength);
23+
}
24+
}
25+
26+
function lastNCharacters(str: string, n: number) {
27+
const startIndex = Math.max(str.length - n, 0);
28+
return str.substring(startIndex);
29+
}
30+
531
export class CrossRegionSupportConstruct extends cdk.Construct {
632
public readonly replicationBucket: s3.IBucket;
733

834
constructor(scope: cdk.Construct, id: string) {
935
super(scope, id);
1036

1137
const encryptionKey = new kms.Key(this, 'CrossRegionCodePipelineReplicationBucketEncryptionKey');
12-
const encryptionAlias = new kms.Alias(this, 'CrossRegionCodePipelineReplicationBucketEncryptionAlias', {
38+
const encryptionAlias = new AliasWithShorterGeneratedName(this, 'CrossRegionCodePipelineReplicationBucketEncryptionAlias', {
1339
targetKey: encryptionKey,
1440
aliasName: cdk.PhysicalName.GENERATE_IF_NEEDED,
1541
removalPolicy: cdk.RemovalPolicy.RETAIN,

packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts

+27-15
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ export class Pipeline extends PipelineBase {
206206
private readonly stages = new Array<Stage>();
207207
private readonly crossRegionBucketsPassed: boolean;
208208
private readonly _crossRegionSupport: { [region: string]: CrossRegionSupport } = {};
209+
private readonly _crossAccountSupport: { [account: string]: Stack } = {};
209210

210211
constructor(scope: Construct, id: string, props: PipelineProps = {}) {
211212
super(scope, id, {
@@ -379,7 +380,11 @@ export class Pipeline extends PipelineBase {
379380
const actionResourceStack = Stack.of(actionResource);
380381
if (pipelineStack.region !== actionResourceStack.region) {
381382
actionRegion = actionResourceStack.region;
382-
otherStack = actionResourceStack;
383+
// if the resource is from a different stack in another region but the same account,
384+
// use that stack as home for the cross-region support resources
385+
if (pipelineStack.account === actionResourceStack.account) {
386+
otherStack = actionResourceStack;
387+
}
383388
}
384389
} else {
385390
actionRegion = action.actionProperties.region;
@@ -570,9 +575,12 @@ export class Pipeline extends PipelineBase {
570575
if (action.actionProperties.resource) {
571576
const resourceStack = Stack.of(action.actionProperties.resource);
572577
// check if resource is from a different account
573-
return pipelineStack.account === resourceStack.account
574-
? undefined
575-
: resourceStack;
578+
if (pipelineStack.account === resourceStack.account) {
579+
return undefined;
580+
} else {
581+
this._crossAccountSupport[resourceStack.account] = resourceStack;
582+
return resourceStack;
583+
}
576584
}
577585

578586
if (!action.actionProperties.account) {
@@ -593,17 +601,21 @@ export class Pipeline extends PipelineBase {
593601
return undefined;
594602
}
595603

596-
const stackId = `cross-account-support-stack-${targetAccount}`;
597-
const app = this.requireApp();
598-
let targetAccountStack = app.node.tryFindChild(stackId) as Stack;
604+
let targetAccountStack: Stack | undefined = this._crossAccountSupport[targetAccount];
599605
if (!targetAccountStack) {
600-
targetAccountStack = new Stack(app, stackId, {
601-
stackName: `${pipelineStack.stackName}-support-${targetAccount}`,
602-
env: {
603-
account: targetAccount,
604-
region: action.actionProperties.region ? action.actionProperties.region : pipelineStack.region,
605-
},
606-
});
606+
const stackId = `cross-account-support-stack-${targetAccount}`;
607+
const app = this.requireApp();
608+
targetAccountStack = app.node.tryFindChild(stackId) as Stack;
609+
if (!targetAccountStack) {
610+
targetAccountStack = new Stack(app, stackId, {
611+
stackName: `${pipelineStack.stackName}-support-${targetAccount}`,
612+
env: {
613+
account: targetAccount,
614+
region: action.actionProperties.region ? action.actionProperties.region : pipelineStack.region,
615+
},
616+
});
617+
}
618+
this._crossAccountSupport[targetAccount] = targetAccountStack;
607619
}
608620
return targetAccountStack;
609621
}
@@ -752,7 +764,7 @@ export class Pipeline extends PipelineBase {
752764
if (bucketKey) {
753765
encryptionKey = {
754766
type: 'KMS',
755-
id: bucketKey.keyId,
767+
id: bucketKey.keyArn,
756768
};
757769
}
758770

packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts

+26-4
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,18 @@ export = {
116116
"Type": "S3",
117117
"EncryptionKey": {
118118
"Type": "KMS",
119-
"Id": "alias/my-replication-alias",
119+
"Id": {
120+
"Fn::Join": [
121+
"",
122+
[
123+
"arn:",
124+
{
125+
"Ref": "AWS::Partition",
126+
},
127+
":kms:us-west-1:123456789012:alias/my-replication-alias",
128+
],
129+
],
130+
},
120131
},
121132
},
122133
},
@@ -163,7 +174,7 @@ export = {
163174
test.done();
164175
},
165176

166-
"generates ArtifactStores with the alias' name as the KeyID"(test: Test) {
177+
"generates ArtifactStores with the alias' ARN as the KeyID"(test: Test) {
167178
const app = new cdk.App();
168179
const replicationRegion = 'us-west-1';
169180

@@ -200,7 +211,18 @@ export = {
200211
"Type": "S3",
201212
"EncryptionKey": {
202213
"Type": "KMS",
203-
"Id": "alias/mystack-support-us-west-1tencryptionalias9b344b2b8e6825cb1f7d",
214+
"Id": {
215+
"Fn::Join": [
216+
"",
217+
[
218+
"arn:",
219+
{
220+
"Ref": "AWS::Partition",
221+
},
222+
":kms:us-west-1:123456789012:alias/s-west-1tencryptionalias9b344b2b8e6825cb1f7d",
223+
],
224+
],
225+
},
204226
},
205227
},
206228
},
@@ -262,7 +284,7 @@ export = {
262284
"Location": "my-us-west-1-replication-bucket",
263285
"EncryptionKey": {
264286
"Type": "KMS",
265-
"Id": "1234-5678-9012",
287+
"Id": "arn:aws:kms:us-west-1:123456789012:key/1234-5678-9012",
266288
},
267289
},
268290
},

packages/@aws-cdk/aws-events-targets/test/codepipeline/integ.pipeline-event-target.expected.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,10 @@
282282
"ArtifactStore": {
283283
"EncryptionKey": {
284284
"Id": {
285-
"Ref": "pipelinePipeline22F2A91DArtifactsBucketEncryptionKey87C796D2"
285+
"Fn::GetAtt": [
286+
"pipelinePipeline22F2A91DArtifactsBucketEncryptionKey87C796D2",
287+
"Arn"
288+
]
286289
},
287290
"Type": "KMS"
288291
},

0 commit comments

Comments
 (0)