Skip to content

Commit d09d30c

Browse files
authored
feat(aws-codecommit): use CloudWatch Events instead of polling by default in the CodePipeline Action. (#1026)
BREAKING CHANGE: this modifies the default behavior of the CodeCommit Action. It also changes the internal API contract between the aws-codepipeline-api module and the CodePipeline Actions in the service packages.
1 parent fdabe95 commit d09d30c

18 files changed

+261
-104
lines changed

packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class PipelineExecuteChangeSetAction extends PipelineCloudFormationAction
9292
ChangeSetName: props.changeSetName,
9393
});
9494

95-
SingletonPolicy.forRole(props.stage.pipelineRole)
95+
SingletonPolicy.forRole(props.stage.pipeline.role)
9696
.grantExecuteChangeSet(props);
9797
}
9898
}
@@ -210,7 +210,7 @@ export abstract class PipelineCloudFormationDeployAction extends PipelineCloudFo
210210
}
211211
}
212212

213-
SingletonPolicy.forRole(props.stage.pipelineRole).grantPassRole(this.role);
213+
SingletonPolicy.forRole(props.stage.pipeline.role).grantPassRole(this.role);
214214
}
215215

216216
/**
@@ -255,7 +255,7 @@ export class PipelineCreateReplaceChangeSetAction extends PipelineCloudFormation
255255
this.addInputArtifact(props.templateConfiguration.artifact);
256256
}
257257

258-
SingletonPolicy.forRole(props.stage.pipelineRole).grantCreateReplaceChangeSet(props);
258+
SingletonPolicy.forRole(props.stage.pipeline.role).grantCreateReplaceChangeSet(props);
259259
}
260260
}
261261

@@ -310,7 +310,7 @@ export class PipelineCreateUpdateStackAction extends PipelineCloudFormationDeplo
310310
this.addInputArtifact(props.templateConfiguration.artifact);
311311
}
312312

313-
SingletonPolicy.forRole(props.stage.pipelineRole).grantCreateUpdateStack(props);
313+
SingletonPolicy.forRole(props.stage.pipeline.role).grantCreateUpdateStack(props);
314314
}
315315
}
316316

@@ -332,7 +332,7 @@ export class PipelineDeleteStackAction extends PipelineCloudFormationDeployActio
332332
super(parent, id, props, {
333333
ActionMode: 'DELETE_ONLY',
334334
});
335-
SingletonPolicy.forRole(props.stage.pipelineRole).grantDeleteStack(props);
335+
SingletonPolicy.forRole(props.stage.pipeline.role).grantDeleteStack(props);
336336
}
337337
}
338338

packages/@aws-cdk/aws-cloudformation/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"license": "Apache-2.0",
5858
"devDependencies": {
5959
"@aws-cdk/assert": "^0.14.1",
60+
"@aws-cdk/aws-events": "^0.14.1",
6061
"@types/lodash": "^4.14.116",
6162
"cdk-build-tools": "^0.14.1",
6263
"cdk-integ-tools": "^0.14.1",

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

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import cpapi = require('@aws-cdk/aws-codepipeline-api');
2+
import events = require('@aws-cdk/aws-events');
23
import iam = require('@aws-cdk/aws-iam');
34
import cdk = require('@aws-cdk/cdk');
45
import _ = require('lodash');
@@ -10,7 +11,7 @@ export = nodeunit.testCase({
1011
'works'(test: nodeunit.Test) {
1112
const stack = new cdk.Stack();
1213
const pipelineRole = new RoleDouble(stack, 'PipelineRole');
13-
const stage = new StageDouble({ pipelineRole });
14+
const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) });
1415
const artifact = new cpapi.Artifact(stack as any, 'TestArtifact');
1516
const action = new cloudformation.PipelineCreateReplaceChangeSetAction(stack, 'Action', {
1617
stage,
@@ -43,7 +44,7 @@ export = nodeunit.testCase({
4344
'uses a single permission statement if the same ChangeSet name is used'(test: nodeunit.Test) {
4445
const stack = new cdk.Stack();
4546
const pipelineRole = new RoleDouble(stack, 'PipelineRole');
46-
const stage = new StageDouble({ pipelineRole });
47+
const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) });
4748
const artifact = new cpapi.Artifact(stack as any, 'TestArtifact');
4849
new cloudformation.PipelineCreateReplaceChangeSetAction(stack, 'ActionA', {
4950
stage,
@@ -97,7 +98,7 @@ export = nodeunit.testCase({
9798
'works'(test: nodeunit.Test) {
9899
const stack = new cdk.Stack();
99100
const pipelineRole = new RoleDouble(stack, 'PipelineRole');
100-
const stage = new StageDouble({ pipelineRole });
101+
const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) });
101102
new cloudformation.PipelineExecuteChangeSetAction(stack, 'Action', {
102103
stage,
103104
changeSetName: 'MyChangeSet',
@@ -120,7 +121,7 @@ export = nodeunit.testCase({
120121
'uses a single permission statement if the same ChangeSet name is used'(test: nodeunit.Test) {
121122
const stack = new cdk.Stack();
122123
const pipelineRole = new RoleDouble(stack, 'PipelineRole');
123-
const stage = new StageDouble({ pipelineRole });
124+
const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) });
124125
new cloudformation.PipelineExecuteChangeSetAction(stack, 'ActionA', {
125126
stage,
126127
changeSetName: 'MyChangeSet',
@@ -158,7 +159,7 @@ export = nodeunit.testCase({
158159
const stack = new cdk.Stack();
159160
const pipelineRole = new RoleDouble(stack, 'PipelineRole');
160161
const action = new cloudformation.PipelineCreateUpdateStackAction(stack, 'Action', {
161-
stage: new StageDouble({ pipelineRole }),
162+
stage: new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }),
162163
templatePath: new cpapi.Artifact(stack as any, 'TestArtifact').atPath('some/file'),
163164
stackName: 'MyStack',
164165
replaceOnFailure: true,
@@ -179,7 +180,7 @@ export = nodeunit.testCase({
179180
const stack = new cdk.Stack();
180181
const pipelineRole = new RoleDouble(stack, 'PipelineRole');
181182
const action = new cloudformation.PipelineDeleteStackAction(stack, 'Action', {
182-
stage: new StageDouble({ pipelineRole }),
183+
stage: new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }),
183184
stackName: 'MyStack',
184185
});
185186
const stackArn = _stackArn('MyStack');
@@ -273,26 +274,42 @@ function _stackArn(stackName: string): string {
273274
});
274275
}
275276

276-
class StageDouble implements cpapi.IStage, cpapi.IInternalStage {
277-
public readonly name: string;
277+
class PipelineDouble implements cpapi.IPipeline {
278278
public readonly pipelineArn: string;
279-
public readonly pipelineRole: iam.Role;
280-
public readonly _internal = this;
279+
public readonly role: iam.Role;
281280

282-
public readonly actions = new Array<cpapi.Action>();
283-
284-
constructor({ name, pipelineName, pipelineRole }: { name?: string, pipelineName?: string, pipelineRole: iam.Role }) {
285-
this.name = name || 'TestStage';
281+
constructor({ pipelineName, role }: { pipelineName?: string, role: iam.Role }) {
286282
this.pipelineArn = cdk.ArnUtils.fromComponents({ service: 'codepipeline', resource: 'pipeline', resourceName: pipelineName || 'TestPipeline' });
287-
this.pipelineRole = pipelineRole;
283+
this.role = role;
288284
}
289285

290-
public grantPipelineBucketRead() {
291-
throw new Error('Unsupported');
286+
public get uniqueId(): string {
287+
throw new Error("Unsupported");
292288
}
293289

294-
public grantPipelineBucketReadWrite() {
295-
throw new Error('Unsupported');
290+
public grantBucketRead(): void {
291+
throw new Error("Unsupported");
292+
}
293+
294+
public grantBucketReadWrite(): void {
295+
throw new Error("Unsupported");
296+
}
297+
298+
public asEventRuleTarget(): events.EventRuleTargetProps {
299+
throw new Error("Unsupported");
300+
}
301+
}
302+
303+
class StageDouble implements cpapi.IStage, cpapi.IInternalStage {
304+
public readonly name: string;
305+
public readonly pipeline: cpapi.IPipeline;
306+
public readonly _internal = this;
307+
308+
public readonly actions = new Array<cpapi.Action>();
309+
310+
constructor({ name, pipeline }: { name?: string, pipeline: cpapi.IPipeline }) {
311+
this.name = name || 'TestStage';
312+
this.pipeline = pipeline;
296313
}
297314

298315
public _attachAction(action: cpapi.Action) {

packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export class PipelineTestAction extends codepipeline.TestAction {
108108
function setCodeBuildNeededPermissions(stage: codepipeline.IStage, project: ProjectRef,
109109
needsPipelineBucketWrite: boolean) {
110110
// grant the Pipeline role the required permissions to this Project
111-
stage.pipelineRole.addToPolicy(new iam.PolicyStatement()
111+
stage.pipeline.role.addToPolicy(new iam.PolicyStatement()
112112
.addResource(project.projectArn)
113113
.addActions(
114114
'codebuild:BatchGetBuilds',
@@ -117,11 +117,9 @@ function setCodeBuildNeededPermissions(stage: codepipeline.IStage, project: Proj
117117
));
118118

119119
// allow the Project access to the Pipline's artifact Bucket
120-
if (project.role) {
121-
if (needsPipelineBucketWrite) {
122-
stage.grantPipelineBucketReadWrite(project.role);
123-
} else {
124-
stage.grantPipelineBucketRead(project.role);
125-
}
120+
if (needsPipelineBucketWrite) {
121+
stage.pipeline.grantBucketReadWrite(project.role);
122+
} else {
123+
stage.pipeline.grantBucketRead(project.role);
126124
}
127125
}

packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ export interface CommonPipelineSourceActionProps extends codepipeline.CommonActi
2222
*/
2323
branch?: string;
2424

25-
// TODO: use CloudWatch events instead
2625
/**
27-
* Whether or not AWS CodePipeline should poll for source changes.
26+
* Whether AWS CodePipeline should poll for source changes.
27+
* If this is `false`, the Pipeline will use CloudWatch Events to detect source changes instead.
2828
*
29-
* @default true
29+
* @default false
3030
*/
3131
pollForSourceChanges?: boolean;
3232
}
@@ -54,11 +54,15 @@ export class PipelineSourceAction extends codepipeline.SourceAction {
5454
configuration: {
5555
RepositoryName: props.repository.repositoryName,
5656
BranchName: props.branch || 'master',
57-
PollForSourceChanges: props.pollForSourceChanges !== undefined ? props.pollForSourceChanges : true
57+
PollForSourceChanges: props.pollForSourceChanges || false,
5858
},
5959
outputArtifactName: props.outputArtifactName
6060
});
6161

62+
if (!props.pollForSourceChanges) {
63+
props.repository.onCommit(props.stage.pipeline.uniqueId + 'EventRule', props.stage.pipeline, props.branch || 'master');
64+
}
65+
6266
// https://docs.aws.amazon.com/codecommit/latest/userguide/auth-and-access-control-permissions-reference.html#aa-acp
6367
const actions = [
6468
'codecommit:GetBranch',
@@ -68,7 +72,7 @@ export class PipelineSourceAction extends codepipeline.SourceAction {
6872
'codecommit:CancelUploadArchive',
6973
];
7074

71-
props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement()
75+
props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement()
7276
.addResource(props.repository.repositoryArn)
7377
.addActions(...actions));
7478
}

packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class PipelineDeployAction extends codepipeline.DeployAction {
5454
resourceName: props.applicationName,
5555
sep: ':',
5656
});
57-
props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement()
57+
props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement()
5858
.addResource(applicationArn)
5959
.addActions(
6060
'codedeploy:GetApplicationRevision',
@@ -67,7 +67,7 @@ export class PipelineDeployAction extends codepipeline.DeployAction {
6767
resourceName: `${props.applicationName}/${props.deploymentGroupName}`,
6868
sep: ':',
6969
});
70-
props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement()
70+
props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement()
7171
.addResource(deploymentGroupArn)
7272
.addActions(
7373
'codedeploy:CreateDeployment',
@@ -80,7 +80,7 @@ export class PipelineDeployAction extends codepipeline.DeployAction {
8080
resourceName: '*',
8181
sep: ':',
8282
});
83-
props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement()
83+
props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement()
8484
.addResource(deployConfigArn)
8585
.addActions(
8686
'codedeploy:GetDeploymentConfig',

packages/@aws-cdk/aws-codepipeline-api/lib/action.ts

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,42 +68,59 @@ export interface IInternalStage {
6868
}
6969

7070
/**
71-
* The abstract interface of a Pipeline Stage that is used by Actions.
71+
* The abstract view of an AWS CodePipeline as required and used by Actions.
72+
* It extends {@link events.IEventRuleTarget},
73+
* so this interface can be used as a Target for CloudWatch Events.
7274
*/
73-
export interface IStage {
74-
/**
75-
* The physical, human-readable name of this Pipeline Stage.
76-
*/
77-
readonly name: string;
78-
75+
export interface IPipeline extends events.IEventRuleTarget {
7976
/**
8077
* The ARN of the Pipeline.
8178
*/
8279
readonly pipelineArn: string;
8380

8481
/**
85-
* The service Role of the Pipeline.
82+
* The unique ID of the Pipeline Construct.
8683
*/
87-
readonly pipelineRole: iam.Role;
84+
readonly uniqueId: string;
8885

8986
/**
90-
* The API of Stage used internally by the CodePipeline Construct.
91-
* You should never need to call any of the methods inside of it yourself.
87+
* The service Role of the Pipeline.
9288
*/
93-
readonly _internal: IInternalStage;
89+
readonly role: iam.Role;
9490

9591
/* Grants read permissions to the Pipeline's S3 Bucket to the given Identity.
9692
*
9793
* @param identity the IAM Identity to grant the permissions to
9894
*/
99-
grantPipelineBucketRead(identity: iam.IPrincipal): void;
95+
grantBucketRead(identity?: iam.IPrincipal): void;
10096

10197
/**
10298
* Grants read & write permissions to the Pipeline's S3 Bucket to the given Identity.
10399
*
104100
* @param identity the IAM Identity to grant the permissions to
105101
*/
106-
grantPipelineBucketReadWrite(identity: iam.IPrincipal): void;
102+
grantBucketReadWrite(identity?: iam.IPrincipal): void;
103+
}
104+
105+
/**
106+
* The abstract interface of a Pipeline Stage that is used by Actions.
107+
*/
108+
export interface IStage {
109+
/**
110+
* The physical, human-readable name of this Pipeline Stage.
111+
*/
112+
readonly name: string;
113+
114+
/**
115+
* The Pipeline this Stage belongs to.
116+
*/
117+
readonly pipeline: IPipeline;
118+
119+
/**
120+
* The API of Stage used internally by the CodePipeline Construct.
121+
* You should never need to call any of the methods inside of it yourself.
122+
*/
123+
readonly _internal: IInternalStage;
107124
}
108125

109126
/**
@@ -215,7 +232,7 @@ export abstract class Action extends cdk.Construct {
215232
rule.addEventPattern({
216233
detailType: [ 'CodePipeline Stage Execution State Change' ],
217234
source: [ 'aws.codepipeline' ],
218-
resources: [ this.stage.pipelineArn ],
235+
resources: [ this.stage.pipeline.pipelineArn ],
219236
detail: {
220237
stage: [ this.stage.name ],
221238
action: [ this.id ],

0 commit comments

Comments
 (0)