Skip to content

Commit

Permalink
feat(codepipeline): support cross-environment deployments for all act…
Browse files Browse the repository at this point in the history
…ions (#4276)

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
  • Loading branch information
skinny85 authored and mergify[bot] committed Oct 7, 2019
1 parent af79102 commit 1eebf92
Show file tree
Hide file tree
Showing 24 changed files with 240 additions and 54 deletions.
8 changes: 6 additions & 2 deletions packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2');
import iam = require('@aws-cdk/aws-iam');
import sns = require('@aws-cdk/aws-sns');

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

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

Expand Down Expand Up @@ -1091,4 +1095,4 @@ function stringifyNumber(x: number) {
} else {
return `${x}`;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class CodeCommitSourceAction extends Action {

super({
...props,
resource: props.repository,
category: codepipeline.ActionCategory.SOURCE,
provider: 'CodeCommit',
artifactBounds: sourceArtifactBounds(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class CodeDeployServerDeployAction extends Action {
constructor(props: CodeDeployServerDeployActionProps) {
super({
...props,
resource: props.deploymentGroup,
category: codepipeline.ActionCategory.DEPLOY,
provider: 'CodeDeploy',
artifactBounds: deployArtifactBounds(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class EcrSourceAction extends Action {
constructor(props: EcrSourceActionProps) {
super({
...props,
resource: props.repository,
category: codepipeline.ActionCategory.SOURCE,
provider: 'ECR',
artifactBounds: sourceArtifactBounds(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class LambdaInvokeAction extends Action {
constructor(props: LambdaInvokeActionProps) {
super({
...props,
resource: props.lambda,
category: codepipeline.ActionCategory.INVOKE,
provider: 'Lambda',
artifactBounds: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class S3DeployAction extends Action {
constructor(props: S3DeployActionProps) {
super({
...props,
resource: props.bucket,
category: codepipeline.ActionCategory.DEPLOY,
provider: 'S3',
artifactBounds: deployArtifactBounds(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export class S3SourceAction extends Action {
constructor(props: S3SourceActionProps) {
super({
...props,
resource: props.bucket,
category: codepipeline.ActionCategory.SOURCE,
provider: 'S3',
artifactBounds: sourceArtifactBounds(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,10 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,10 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,10 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,10 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,10 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,10 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,10 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,10 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Ref": "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3"
"Fn::GetAtt": [
"MyPipelineArtifactsBucketEncryptionKey8BF0A7F3",
"Arn"
]
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,10 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
},
"Type": "KMS"
},
Expand Down
13 changes: 12 additions & 1 deletion packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,18 @@ export = {
"Type": "S3",
"Location": "replicationstackeplicationbucket2464cd5c33b386483b66",
"EncryptionKey": {
"Id": "alias/replicationstacktencryptionalias043cb2f8ceac9da9c07c",
"Id": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition",
},
":kms:us-west-1:123456789012:alias/ionstacktencryptionalias043cb2f8ceac9da9c07c",
],
],
},
"Type": "KMS"
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,40 @@ import kms = require('@aws-cdk/aws-kms');
import s3 = require('@aws-cdk/aws-s3');
import cdk = require('@aws-cdk/core');

const REQUIRED_ALIAS_PREFIX = 'alias/';

/**
* A class needed to work around CodePipeline's extremely small (100 characters)
* limit for the name/ARN of the key in the ArtifactStore.
* Limits the length of the alias' auto-generated name to 50 characters.
*/
class AliasWithShorterGeneratedName extends kms.Alias {
protected generatePhysicalName(): string {
let baseName = super.generatePhysicalName();
if (baseName.startsWith(REQUIRED_ALIAS_PREFIX)) {
// remove the prefix, because we're taking the last characters of the name below
baseName = baseName.substring(REQUIRED_ALIAS_PREFIX.length);
}
const maxLength = 50 - REQUIRED_ALIAS_PREFIX.length;
// take the last characters, as they include the hash,
// and so have a higher chance of not colliding
return REQUIRED_ALIAS_PREFIX + lastNCharacters(baseName, maxLength);
}
}

function lastNCharacters(str: string, n: number) {
const startIndex = Math.max(str.length - n, 0);
return str.substring(startIndex);
}

export class CrossRegionSupportConstruct extends cdk.Construct {
public readonly replicationBucket: s3.IBucket;

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

const encryptionKey = new kms.Key(this, 'CrossRegionCodePipelineReplicationBucketEncryptionKey');
const encryptionAlias = new kms.Alias(this, 'CrossRegionCodePipelineReplicationBucketEncryptionAlias', {
const encryptionAlias = new AliasWithShorterGeneratedName(this, 'CrossRegionCodePipelineReplicationBucketEncryptionAlias', {
targetKey: encryptionKey,
aliasName: cdk.PhysicalName.GENERATE_IF_NEEDED,
removalPolicy: cdk.RemovalPolicy.RETAIN,
Expand Down
42 changes: 27 additions & 15 deletions packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ export class Pipeline extends PipelineBase {
private readonly stages = new Array<Stage>();
private readonly crossRegionBucketsPassed: boolean;
private readonly _crossRegionSupport: { [region: string]: CrossRegionSupport } = {};
private readonly _crossAccountSupport: { [account: string]: Stack } = {};

constructor(scope: Construct, id: string, props: PipelineProps = {}) {
super(scope, id, {
Expand Down Expand Up @@ -379,7 +380,11 @@ export class Pipeline extends PipelineBase {
const actionResourceStack = Stack.of(actionResource);
if (pipelineStack.region !== actionResourceStack.region) {
actionRegion = actionResourceStack.region;
otherStack = actionResourceStack;
// if the resource is from a different stack in another region but the same account,
// use that stack as home for the cross-region support resources
if (pipelineStack.account === actionResourceStack.account) {
otherStack = actionResourceStack;
}
}
} else {
actionRegion = action.actionProperties.region;
Expand Down Expand Up @@ -570,9 +575,12 @@ export class Pipeline extends PipelineBase {
if (action.actionProperties.resource) {
const resourceStack = Stack.of(action.actionProperties.resource);
// check if resource is from a different account
return pipelineStack.account === resourceStack.account
? undefined
: resourceStack;
if (pipelineStack.account === resourceStack.account) {
return undefined;
} else {
this._crossAccountSupport[resourceStack.account] = resourceStack;
return resourceStack;
}
}

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

const stackId = `cross-account-support-stack-${targetAccount}`;
const app = this.requireApp();
let targetAccountStack = app.node.tryFindChild(stackId) as Stack;
let targetAccountStack: Stack | undefined = this._crossAccountSupport[targetAccount];
if (!targetAccountStack) {
targetAccountStack = new Stack(app, stackId, {
stackName: `${pipelineStack.stackName}-support-${targetAccount}`,
env: {
account: targetAccount,
region: action.actionProperties.region ? action.actionProperties.region : pipelineStack.region,
},
});
const stackId = `cross-account-support-stack-${targetAccount}`;
const app = this.requireApp();
targetAccountStack = app.node.tryFindChild(stackId) as Stack;
if (!targetAccountStack) {
targetAccountStack = new Stack(app, stackId, {
stackName: `${pipelineStack.stackName}-support-${targetAccount}`,
env: {
account: targetAccount,
region: action.actionProperties.region ? action.actionProperties.region : pipelineStack.region,
},
});
}
this._crossAccountSupport[targetAccount] = targetAccountStack;
}
return targetAccountStack;
}
Expand Down Expand Up @@ -752,7 +764,7 @@ export class Pipeline extends PipelineBase {
if (bucketKey) {
encryptionKey = {
type: 'KMS',
id: bucketKey.keyId,
id: bucketKey.keyArn,
};
}

Expand Down
30 changes: 26 additions & 4 deletions packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,18 @@ export = {
"Type": "S3",
"EncryptionKey": {
"Type": "KMS",
"Id": "alias/my-replication-alias",
"Id": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition",
},
":kms:us-west-1:123456789012:alias/my-replication-alias",
],
],
},
},
},
},
Expand Down Expand Up @@ -163,7 +174,7 @@ export = {
test.done();
},

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

Expand Down Expand Up @@ -200,7 +211,18 @@ export = {
"Type": "S3",
"EncryptionKey": {
"Type": "KMS",
"Id": "alias/mystack-support-us-west-1tencryptionalias9b344b2b8e6825cb1f7d",
"Id": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition",
},
":kms:us-west-1:123456789012:alias/s-west-1tencryptionalias9b344b2b8e6825cb1f7d",
],
],
},
},
},
},
Expand Down Expand Up @@ -262,7 +284,7 @@ export = {
"Location": "my-us-west-1-replication-bucket",
"EncryptionKey": {
"Type": "KMS",
"Id": "1234-5678-9012",
"Id": "arn:aws:kms:us-west-1:123456789012:key/1234-5678-9012",
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,10 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Ref": "pipelinePipeline22F2A91DArtifactsBucketEncryptionKey87C796D2"
"Fn::GetAtt": [
"pipelinePipeline22F2A91DArtifactsBucketEncryptionKey87C796D2",
"Arn"
]
},
"Type": "KMS"
},
Expand Down
Loading

0 comments on commit 1eebf92

Please sign in to comment.