Skip to content

Commit

Permalink
feat(codepipeline): handle non-CFN cross-region actions (#3777)
Browse files Browse the repository at this point in the history
Adds support for actions other than the CloudFormation ones to be cross-region.

It works similarly to the cross-account support: passing a resource from a different region into a CodePipeline action makes the action implicitly cross-region.

Closes #3387
  • Loading branch information
skinny85 committed Sep 18, 2019
1 parent 244dbdc commit b8b4c4d
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 69 deletions.
95 changes: 86 additions & 9 deletions packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,17 +613,17 @@ export = {
expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', {
"ArtifactStores": [
{
"Region": "us-east-1",
"Region": "us-west-1",
"ArtifactStore": {
"Type": "S3",
"Location": "teststack-support-us-easteplicationbucket1a8063b3cdac6e7e0e73",
"Location": "sfo-replication-bucket",
},
},
{
"Region": "us-west-1",
"Region": "us-east-1",
"ArtifactStore": {
"Type": "S3",
"Location": "sfo-replication-bucket",
"Location": "teststack-support-us-easteplicationbucket1a8063b3cdac6e7e0e73",
},
},
{
Expand Down Expand Up @@ -662,8 +662,8 @@ export = {
],
}));

test.equal(pipeline.crossRegionSupport[pipelineRegion], undefined);
test.equal(pipeline.crossRegionSupport['us-west-1'], undefined);
test.notEqual(pipeline.crossRegionSupport[pipelineRegion], undefined);
test.notEqual(pipeline.crossRegionSupport['us-west-1'], undefined);

const usEast1Support = pipeline.crossRegionSupport['us-east-1'];
test.notEqual(usEast1Support, undefined);
Expand Down Expand Up @@ -746,6 +746,83 @@ export = {

test.done();
},

'allows providing a resource-backed action from a different region directly'(test: Test) {
const account = '123456789012';
const app = new App();

const replicationRegion = 'us-west-1';
const replicationStack = new Stack(app, 'ReplicationStack', { env: { region: replicationRegion, account } });
const project = new codebuild.PipelineProject(replicationStack, 'CodeBuildProject', {
projectName: 'MyCodeBuildProject',
});

const pipelineRegion = 'us-west-2';
const pipelineStack = new Stack(app, 'TestStack', { env: { region: pipelineRegion, account } });
const sourceOutput = new codepipeline.Artifact('SourceOutput');
new codepipeline.Pipeline(pipelineStack, 'MyPipeline', {
stages: [
{
stageName: 'Source',
actions: [new cpactions.CodeCommitSourceAction({
actionName: 'CodeCommitAction',
output: sourceOutput,
repository: codecommit.Repository.fromRepositoryName(pipelineStack, 'Repo', 'my-codecommit-repo'),
})],
},
{
stageName: 'Build',
actions: [new cpactions.CodeBuildAction({
actionName: 'CodeBuildAction',
input: sourceOutput,
project,
})],
},
],
});

expect(pipelineStack).to(haveResourceLike('AWS::CodePipeline::Pipeline', {
"ArtifactStores": [
{
"Region": replicationRegion,
"ArtifactStore": {
"Type": "S3",
"Location": "replicationstackeplicationbucket2464cd5c33b386483b66",
"EncryptionKey": {
"Id": "alias/replicationstacktencryptionalias043cb2f8ceac9da9c07c",
"Type": "KMS"
},
},
},
{
"Region": pipelineRegion,
},
],
"Stages": [
{
"Name": "Source",
},
{
"Name": "Build",
"Actions": [
{
"Name": "CodeBuildAction",
"Region": replicationRegion,
"Configuration": {
"ProjectName": "MyCodeBuildProject",
},
},
],
},
],
}));

expect(replicationStack).to(haveResourceLike('AWS::S3::Bucket', {
"BucketName": "replicationstackeplicationbucket2464cd5c33b386483b66",
}));

test.done();
},
},

'cross-account Pipeline': {
Expand All @@ -769,7 +846,7 @@ export = {
});

const pipelineStack = new Stack(app, 'PipelineStack', {
env: { account: '123456789012', region: 'bermuda-triangle-42' },
env: { account: '123456789012', region: buildRegion },
});
const sourceBucket = new s3.Bucket(pipelineStack, 'ArtifactBucket', {
bucketName: 'source-bucket',
Expand Down Expand Up @@ -859,7 +936,7 @@ export = {
{
"Ref": "AWS::Partition",
},
':s3:::pipelinestackeartifactsbucket5409dc84ec8d21c5e28c',
':s3:::pipelinestackeartifactsbucket5409dc84bb108027cb58',
],
],
},
Expand All @@ -871,7 +948,7 @@ export = {
{
"Ref": "AWS::Partition",
},
':s3:::pipelinestackeartifactsbucket5409dc84ec8d21c5e28c/*',
':s3:::pipelinestackeartifactsbucket5409dc84bb108027cb58/*',
],
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@ import kms = require('@aws-cdk/aws-kms');
import s3 = require('@aws-cdk/aws-s3');
import cdk = require('@aws-cdk/core');

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', {
targetKey: encryptionKey,
aliasName: cdk.PhysicalName.GENERATE_IF_NEEDED,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
this.replicationBucket = new s3.Bucket(this, 'CrossRegionCodePipelineReplicationBucket', {
bucketName: cdk.PhysicalName.GENERATE_IF_NEEDED,
encryptionKey: encryptionAlias,
});
}
}

/**
* Construction properties for {@link CrossRegionSupportStack}.
* This interface is private to the aws-codepipeline package.
Expand Down Expand Up @@ -45,16 +64,8 @@ export class CrossRegionSupportStack extends cdk.Stack {
},
});

const encryptionKey = new kms.Key(this, 'CrossRegionCodePipelineReplicationBucketEncryptionKey');
const encryptionAlias = new kms.Alias(this, 'CrossRegionCodePipelineReplicationBucketEncryptionAlias', {
targetKey: encryptionKey,
aliasName: cdk.PhysicalName.GENERATE_IF_NEEDED,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
this.replicationBucket = new s3.Bucket(this, 'CrossRegionCodePipelineReplicationBucket', {
bucketName: cdk.PhysicalName.GENERATE_IF_NEEDED,
encryptionKey: encryptionAlias,
});
const crossRegionSupportConstruct = new CrossRegionSupportConstruct(this, 'Default');
this.replicationBucket = crossRegionSupportConstruct.replicationBucket;
}
}

Expand Down
17 changes: 12 additions & 5 deletions packages/@aws-cdk/aws-codepipeline/lib/full-action-descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import iam = require('@aws-cdk/aws-iam');
import { ActionArtifactBounds, ActionCategory, ActionConfig, IAction } from './action';
import { Artifact } from './artifact';

export interface FullActionDescriptorProps {
readonly action: IAction;
readonly actionConfig: ActionConfig;
readonly actionRole: iam.IRole | undefined;
readonly actionRegion: string | undefined;
}

/**
* This class is private to the aws-codepipeline package.
*/
Expand All @@ -19,8 +26,8 @@ export class FullActionDescriptor {
public readonly role?: iam.IRole;
public readonly configuration: any;

constructor(action: IAction, actionConfig: ActionConfig, actionRole: iam.IRole | undefined) {
const actionProperties = action.actionProperties;
constructor(props: FullActionDescriptorProps) {
const actionProperties = props.action.actionProperties;
this.actionName = actionProperties.actionName;
this.category = actionProperties.category;
this.owner = actionProperties.owner || 'AWS';
Expand All @@ -30,10 +37,10 @@ export class FullActionDescriptor {
this.artifactBounds = actionProperties.artifactBounds;
this.inputs = deduplicateArtifacts(actionProperties.inputs);
this.outputs = deduplicateArtifacts(actionProperties.outputs);
this.region = actionProperties.region;
this.role = actionProperties.role !== undefined ? actionProperties.role : actionRole;
this.region = props.actionRegion || actionProperties.region;
this.role = actionProperties.role !== undefined ? actionProperties.role : props.actionRole;

this.configuration = actionConfig.configuration;
this.configuration = props.actionConfig.configuration;
}
}

Expand Down

0 comments on commit b8b4c4d

Please sign in to comment.