From d09d30ce825c410213c05205cf5085b6e91c6398 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Fri, 2 Nov 2018 11:23:30 -0700 Subject: [PATCH] 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. --- .../lib/pipeline-actions.ts | 10 +- .../@aws-cdk/aws-cloudformation/package.json | 1 + .../test/test.pipeline-actions.ts | 55 +++++--- .../aws-codebuild/lib/pipeline-actions.ts | 12 +- .../aws-codecommit/lib/pipeline-action.ts | 14 +- .../aws-codedeploy/lib/pipeline-action.ts | 6 +- .../aws-codepipeline-api/lib/action.ts | 47 ++++--- .../@aws-cdk/aws-codepipeline/lib/pipeline.ts | 20 ++- .../@aws-cdk/aws-codepipeline/lib/stage.ts | 39 ++---- .../test/integ.cfn-template-from-repo.lit.ts | 1 + .../test/integ.pipeline-code-commit-build.ts | 1 + .../integ.pipeline-code-commit.expected.json | 126 +++++++++++++++++- .../test/integ.pipeline-events.expected.json | 2 +- .../test/integ.pipeline-events.ts | 1 + .../test.cloudformation-pipeline-actions.ts | 1 + .../aws-codepipeline/test/test.pipeline.ts | 23 ++-- .../aws-lambda/lib/pipeline-action.ts | 4 +- .../@aws-cdk/aws-s3/lib/pipeline-action.ts | 2 +- 18 files changed, 261 insertions(+), 104 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts index 74c3b453d1256..af2ee8ae25e1d 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts @@ -92,7 +92,7 @@ export class PipelineExecuteChangeSetAction extends PipelineCloudFormationAction ChangeSetName: props.changeSetName, }); - SingletonPolicy.forRole(props.stage.pipelineRole) + SingletonPolicy.forRole(props.stage.pipeline.role) .grantExecuteChangeSet(props); } } @@ -210,7 +210,7 @@ export abstract class PipelineCloudFormationDeployAction extends PipelineCloudFo } } - SingletonPolicy.forRole(props.stage.pipelineRole).grantPassRole(this.role); + SingletonPolicy.forRole(props.stage.pipeline.role).grantPassRole(this.role); } /** @@ -255,7 +255,7 @@ export class PipelineCreateReplaceChangeSetAction extends PipelineCloudFormation this.addInputArtifact(props.templateConfiguration.artifact); } - SingletonPolicy.forRole(props.stage.pipelineRole).grantCreateReplaceChangeSet(props); + SingletonPolicy.forRole(props.stage.pipeline.role).grantCreateReplaceChangeSet(props); } } @@ -310,7 +310,7 @@ export class PipelineCreateUpdateStackAction extends PipelineCloudFormationDeplo this.addInputArtifact(props.templateConfiguration.artifact); } - SingletonPolicy.forRole(props.stage.pipelineRole).grantCreateUpdateStack(props); + SingletonPolicy.forRole(props.stage.pipeline.role).grantCreateUpdateStack(props); } } @@ -332,7 +332,7 @@ export class PipelineDeleteStackAction extends PipelineCloudFormationDeployActio super(parent, id, props, { ActionMode: 'DELETE_ONLY', }); - SingletonPolicy.forRole(props.stage.pipelineRole).grantDeleteStack(props); + SingletonPolicy.forRole(props.stage.pipeline.role).grantDeleteStack(props); } } diff --git a/packages/@aws-cdk/aws-cloudformation/package.json b/packages/@aws-cdk/aws-cloudformation/package.json index c86a4cf1ef781..e395b0c42bf1e 100644 --- a/packages/@aws-cdk/aws-cloudformation/package.json +++ b/packages/@aws-cdk/aws-cloudformation/package.json @@ -57,6 +57,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "^0.14.1", + "@aws-cdk/aws-events": "^0.14.1", "@types/lodash": "^4.14.116", "cdk-build-tools": "^0.14.1", "cdk-integ-tools": "^0.14.1", diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts index 11db184b50963..978303b4f308f 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts @@ -1,4 +1,5 @@ import cpapi = require('@aws-cdk/aws-codepipeline-api'); +import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); import _ = require('lodash'); @@ -10,7 +11,7 @@ export = nodeunit.testCase({ 'works'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipelineRole }); + const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }); const artifact = new cpapi.Artifact(stack as any, 'TestArtifact'); const action = new cloudformation.PipelineCreateReplaceChangeSetAction(stack, 'Action', { stage, @@ -43,7 +44,7 @@ export = nodeunit.testCase({ 'uses a single permission statement if the same ChangeSet name is used'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipelineRole }); + const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }); const artifact = new cpapi.Artifact(stack as any, 'TestArtifact'); new cloudformation.PipelineCreateReplaceChangeSetAction(stack, 'ActionA', { stage, @@ -97,7 +98,7 @@ export = nodeunit.testCase({ 'works'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipelineRole }); + const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }); new cloudformation.PipelineExecuteChangeSetAction(stack, 'Action', { stage, changeSetName: 'MyChangeSet', @@ -120,7 +121,7 @@ export = nodeunit.testCase({ 'uses a single permission statement if the same ChangeSet name is used'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipelineRole }); + const stage = new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }); new cloudformation.PipelineExecuteChangeSetAction(stack, 'ActionA', { stage, changeSetName: 'MyChangeSet', @@ -158,7 +159,7 @@ export = nodeunit.testCase({ const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); const action = new cloudformation.PipelineCreateUpdateStackAction(stack, 'Action', { - stage: new StageDouble({ pipelineRole }), + stage: new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }), templatePath: new cpapi.Artifact(stack as any, 'TestArtifact').atPath('some/file'), stackName: 'MyStack', replaceOnFailure: true, @@ -179,7 +180,7 @@ export = nodeunit.testCase({ const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); const action = new cloudformation.PipelineDeleteStackAction(stack, 'Action', { - stage: new StageDouble({ pipelineRole }), + stage: new StageDouble({ pipeline: new PipelineDouble({ role: pipelineRole }) }), stackName: 'MyStack', }); const stackArn = _stackArn('MyStack'); @@ -273,26 +274,42 @@ function _stackArn(stackName: string): string { }); } -class StageDouble implements cpapi.IStage, cpapi.IInternalStage { - public readonly name: string; +class PipelineDouble implements cpapi.IPipeline { public readonly pipelineArn: string; - public readonly pipelineRole: iam.Role; - public readonly _internal = this; + public readonly role: iam.Role; - public readonly actions = new Array(); - - constructor({ name, pipelineName, pipelineRole }: { name?: string, pipelineName?: string, pipelineRole: iam.Role }) { - this.name = name || 'TestStage'; + constructor({ pipelineName, role }: { pipelineName?: string, role: iam.Role }) { this.pipelineArn = cdk.ArnUtils.fromComponents({ service: 'codepipeline', resource: 'pipeline', resourceName: pipelineName || 'TestPipeline' }); - this.pipelineRole = pipelineRole; + this.role = role; } - public grantPipelineBucketRead() { - throw new Error('Unsupported'); + public get uniqueId(): string { + throw new Error("Unsupported"); } - public grantPipelineBucketReadWrite() { - throw new Error('Unsupported'); + public grantBucketRead(): void { + throw new Error("Unsupported"); + } + + public grantBucketReadWrite(): void { + throw new Error("Unsupported"); + } + + public asEventRuleTarget(): events.EventRuleTargetProps { + throw new Error("Unsupported"); + } +} + +class StageDouble implements cpapi.IStage, cpapi.IInternalStage { + public readonly name: string; + public readonly pipeline: cpapi.IPipeline; + public readonly _internal = this; + + public readonly actions = new Array(); + + constructor({ name, pipeline }: { name?: string, pipeline: cpapi.IPipeline }) { + this.name = name || 'TestStage'; + this.pipeline = pipeline; } public _attachAction(action: cpapi.Action) { diff --git a/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts index 4969851ee7b0b..dfb99c76c49f3 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts @@ -108,7 +108,7 @@ export class PipelineTestAction extends codepipeline.TestAction { function setCodeBuildNeededPermissions(stage: codepipeline.IStage, project: ProjectRef, needsPipelineBucketWrite: boolean) { // grant the Pipeline role the required permissions to this Project - stage.pipelineRole.addToPolicy(new iam.PolicyStatement() + stage.pipeline.role.addToPolicy(new iam.PolicyStatement() .addResource(project.projectArn) .addActions( 'codebuild:BatchGetBuilds', @@ -117,11 +117,9 @@ function setCodeBuildNeededPermissions(stage: codepipeline.IStage, project: Proj )); // allow the Project access to the Pipline's artifact Bucket - if (project.role) { - if (needsPipelineBucketWrite) { - stage.grantPipelineBucketReadWrite(project.role); - } else { - stage.grantPipelineBucketRead(project.role); - } + if (needsPipelineBucketWrite) { + stage.pipeline.grantBucketReadWrite(project.role); + } else { + stage.pipeline.grantBucketRead(project.role); } } diff --git a/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts index 87027af072e1e..95f9b3b304453 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts @@ -22,11 +22,11 @@ export interface CommonPipelineSourceActionProps extends codepipeline.CommonActi */ branch?: string; - // TODO: use CloudWatch events instead /** - * Whether or not AWS CodePipeline should poll for source changes. + * Whether AWS CodePipeline should poll for source changes. + * If this is `false`, the Pipeline will use CloudWatch Events to detect source changes instead. * - * @default true + * @default false */ pollForSourceChanges?: boolean; } @@ -54,11 +54,15 @@ export class PipelineSourceAction extends codepipeline.SourceAction { configuration: { RepositoryName: props.repository.repositoryName, BranchName: props.branch || 'master', - PollForSourceChanges: props.pollForSourceChanges !== undefined ? props.pollForSourceChanges : true + PollForSourceChanges: props.pollForSourceChanges || false, }, outputArtifactName: props.outputArtifactName }); + if (!props.pollForSourceChanges) { + props.repository.onCommit(props.stage.pipeline.uniqueId + 'EventRule', props.stage.pipeline, props.branch || 'master'); + } + // https://docs.aws.amazon.com/codecommit/latest/userguide/auth-and-access-control-permissions-reference.html#aa-acp const actions = [ 'codecommit:GetBranch', @@ -68,7 +72,7 @@ export class PipelineSourceAction extends codepipeline.SourceAction { 'codecommit:CancelUploadArchive', ]; - props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement() + props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() .addResource(props.repository.repositoryArn) .addActions(...actions)); } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts index 647abd1ae93c7..656909c9cd0ef 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts @@ -54,7 +54,7 @@ export class PipelineDeployAction extends codepipeline.DeployAction { resourceName: props.applicationName, sep: ':', }); - props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement() + props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() .addResource(applicationArn) .addActions( 'codedeploy:GetApplicationRevision', @@ -67,7 +67,7 @@ export class PipelineDeployAction extends codepipeline.DeployAction { resourceName: `${props.applicationName}/${props.deploymentGroupName}`, sep: ':', }); - props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement() + props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() .addResource(deploymentGroupArn) .addActions( 'codedeploy:CreateDeployment', @@ -80,7 +80,7 @@ export class PipelineDeployAction extends codepipeline.DeployAction { resourceName: '*', sep: ':', }); - props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement() + props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() .addResource(deployConfigArn) .addActions( 'codedeploy:GetDeploymentConfig', diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts index c43d013e4ac3e..67df1ecfdcbce 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts @@ -68,42 +68,59 @@ export interface IInternalStage { } /** - * The abstract interface of a Pipeline Stage that is used by Actions. + * The abstract view of an AWS CodePipeline as required and used by Actions. + * It extends {@link events.IEventRuleTarget}, + * so this interface can be used as a Target for CloudWatch Events. */ -export interface IStage { - /** - * The physical, human-readable name of this Pipeline Stage. - */ - readonly name: string; - +export interface IPipeline extends events.IEventRuleTarget { /** * The ARN of the Pipeline. */ readonly pipelineArn: string; /** - * The service Role of the Pipeline. + * The unique ID of the Pipeline Construct. */ - readonly pipelineRole: iam.Role; + readonly uniqueId: string; /** - * The API of Stage used internally by the CodePipeline Construct. - * You should never need to call any of the methods inside of it yourself. + * The service Role of the Pipeline. */ - readonly _internal: IInternalStage; + readonly role: iam.Role; /* Grants read permissions to the Pipeline's S3 Bucket to the given Identity. * * @param identity the IAM Identity to grant the permissions to */ - grantPipelineBucketRead(identity: iam.IPrincipal): void; + grantBucketRead(identity?: iam.IPrincipal): void; /** * Grants read & write permissions to the Pipeline's S3 Bucket to the given Identity. * * @param identity the IAM Identity to grant the permissions to */ - grantPipelineBucketReadWrite(identity: iam.IPrincipal): void; + grantBucketReadWrite(identity?: iam.IPrincipal): void; +} + +/** + * The abstract interface of a Pipeline Stage that is used by Actions. + */ +export interface IStage { + /** + * The physical, human-readable name of this Pipeline Stage. + */ + readonly name: string; + + /** + * The Pipeline this Stage belongs to. + */ + readonly pipeline: IPipeline; + + /** + * The API of Stage used internally by the CodePipeline Construct. + * You should never need to call any of the methods inside of it yourself. + */ + readonly _internal: IInternalStage; } /** @@ -215,7 +232,7 @@ export abstract class Action extends cdk.Construct { rule.addEventPattern({ detailType: [ 'CodePipeline Stage Execution State Change' ], source: [ 'aws.codepipeline' ], - resources: [ this.stage.pipelineArn ], + resources: [ this.stage.pipeline.pipelineArn ], detail: { stage: [ this.stage.name ], action: [ this.id ], diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index 04d653a000064..2f432f07eb740 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -1,4 +1,4 @@ -import actions = require('@aws-cdk/aws-codepipeline-api'); +import cpapi = require('@aws-cdk/aws-codepipeline-api'); import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); @@ -43,7 +43,7 @@ export interface PipelineProps { * * // ... add more stages */ -export class Pipeline extends cdk.Construct implements events.IEventRuleTarget { +export class Pipeline extends cdk.Construct implements cpapi.IPipeline { /** * The IAM role AWS CodePipeline will use to perform actions or assume roles for actions with * a more specific IAM role. @@ -77,7 +77,7 @@ export class Pipeline extends cdk.Construct implements events.IEventRuleTarget { super(parent, name); props = props || {}; - actions.validateName('Pipeline', props.pipelineName); + cpapi.validateName('Pipeline', props.pipelineName); // If a bucket has been provided, use it - otherwise, create a bucket. let propsBucket = props.artifactBucket; @@ -216,6 +216,14 @@ export class Pipeline extends cdk.Construct implements events.IEventRuleTarget { return this.stages.length; } + public grantBucketRead(identity?: iam.IPrincipal): void { + this.artifactBucket.grantRead(identity); + } + + public grantBucketReadWrite(identity?: iam.IPrincipal): void { + this.artifactBucket.grantReadWrite(identity); + } + /** * Adds a Stage to this Pipeline. * This is an internal operation - @@ -247,7 +255,7 @@ export class Pipeline extends cdk.Construct implements events.IEventRuleTarget { // ignore unused private method (it's actually used in Stage) // @ts-ignore - private _generateOutputArtifactName(stage: actions.IStage, action: actions.Action): string { + private _generateOutputArtifactName(stage: cpapi.IStage, action: cpapi.Action): string { // generate the artifact name based on the Action's full logical ID, // thus guaranteeing uniqueness return 'Artifact_' + action.uniqueId; @@ -265,7 +273,7 @@ export class Pipeline extends cdk.Construct implements events.IEventRuleTarget { */ // ignore unused private method (it's actually used in Stage) // @ts-ignore - private _findInputArtifact(stage: actions.IStage, action: actions.Action): actions.Artifact { + private _findInputArtifact(stage: cpapi.IStage, action: cpapi.Action): cpapi.Artifact { // search for the first Action that has an outputArtifact, // and return that const startIndex = this.stages.findIndex(s => s === stage); @@ -337,7 +345,7 @@ export class Pipeline extends cdk.Construct implements events.IEventRuleTarget { for (const stage of this.stages) { const onlySourceActionsPermitted = firstStage; for (const action of stage.actions) { - errors.push(...actions.validateSourceAction(onlySourceActionsPermitted, action.category, action.id, stage.id)); + errors.push(...cpapi.validateSourceAction(onlySourceActionsPermitted, action.category, action.id, stage.id)); } firstStage = false; } diff --git a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts index 05e4df56f81cd..2105aa741d35c 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts @@ -1,6 +1,5 @@ -import actions = require('@aws-cdk/aws-codepipeline-api'); +import cpapi = require('@aws-cdk/aws-codepipeline-api'); import events = require('@aws-cdk/aws-events'); -import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); import { cloudformation } from './codepipeline.generated'; import { Pipeline } from './pipeline'; @@ -72,11 +71,11 @@ export interface StageProps extends CommonStageProps { * pipeline: myPipeline, * }); */ -export class Stage extends cdk.Construct implements actions.IStage, actions.IInternalStage { +export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInternalStage { /** * The Pipeline this Stage is a part of. */ - public readonly pipeline: Pipeline; + public readonly pipeline: cpapi.IPipeline; public readonly name: string; /** @@ -85,7 +84,7 @@ export class Stage extends cdk.Construct implements actions.IStage, actions.IInt */ public readonly _internal = this; - private readonly _actions = new Array(); + private readonly _actions = new Array(); /** * Create a new Stage. @@ -94,7 +93,7 @@ export class Stage extends cdk.Construct implements actions.IStage, actions.IInt super(parent, name); this.name = name; this.pipeline = props.pipeline; - actions.validateName('Stage', name); + cpapi.validateName('Stage', name); (this.pipeline as any)._attachStage(this, props.placement); } @@ -102,7 +101,7 @@ export class Stage extends cdk.Construct implements actions.IStage, actions.IInt /** * Get a duplicate of this stage's list of actions. */ - public get actions(): actions.Action[] { + public get actions(): cpapi.Action[] { return this._actions.slice(); } @@ -110,14 +109,6 @@ export class Stage extends cdk.Construct implements actions.IStage, actions.IInt return this.validateHasActions(); } - public grantPipelineBucketRead(identity: iam.IPrincipal): void { - this.pipeline.artifactBucket.grantRead(identity); - } - - public grantPipelineBucketReadWrite(identity: iam.IPrincipal): void { - this.pipeline.artifactBucket.grantReadWrite(identity); - } - public render(): cloudformation.PipelineResource.StageDeclarationProperty { return { name: this.id, @@ -126,7 +117,7 @@ export class Stage extends cdk.Construct implements actions.IStage, actions.IInt } public onStateChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) { - const rule = new events.EventRule(this.pipeline, name, options); + const rule = new events.EventRule(this, name, options); rule.addTarget(target); rule.addEventPattern({ detailType: [ 'CodePipeline Stage Execution State Change' ], @@ -139,32 +130,24 @@ export class Stage extends cdk.Construct implements actions.IStage, actions.IInt return rule; } - public get pipelineArn(): string { - return this.pipeline.pipelineArn; - } - - public get pipelineRole(): iam.Role { - return this.pipeline.role; - } - // can't make this method private like Pipeline#_attachStage, // as it comes from the IStage interface - public _attachAction(action: actions.Action): void { + public _attachAction(action: cpapi.Action): void { // _attachAction should be idempotent in case a customer ever calls it directly if (!this._actions.includes(action)) { this._actions.push(action); } } - public _generateOutputArtifactName(action: actions.Action): string { + public _generateOutputArtifactName(action: cpapi.Action): string { return (this.pipeline as any)._generateOutputArtifactName(this, action); } - public _findInputArtifact(action: actions.Action): actions.Artifact { + public _findInputArtifact(action: cpapi.Action): cpapi.Artifact { return (this.pipeline as any)._findInputArtifact(this, action); } - private renderAction(action: actions.Action): cloudformation.PipelineResource.ActionDeclarationProperty { + private renderAction(action: cpapi.Action): cloudformation.PipelineResource.ActionDeclarationProperty { return { name: action.id, inputArtifacts: action._inputArtifacts.map(a => ({ name: a.name })), diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts index 99c9cdb23dc2f..4ac08dbc0ab9a 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts @@ -18,6 +18,7 @@ const source = new codecommit.PipelineSourceAction(stack, 'Source', { stage: sourceStage, repository: repo, outputArtifactName: 'SourceArtifact', + pollForSourceChanges: true, }); // Deployment stage: create and deploy changeset with manual approval diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.ts index 28292f090f095..da91ed58875d4 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.ts @@ -18,6 +18,7 @@ new codecommit.PipelineSourceAction(stack, 'source', { stage: sourceStage, outputArtifactName: 'SourceArtifact', repository, + pollForSourceChanges: true, }); const project = new codebuild.Project(stack, 'MyBuildProject', { diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.expected.json index 52f99453d0776..94dca9257ab14 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.expected.json @@ -7,6 +7,70 @@ "Triggers": [] } }, + "MyRepoawscdkcodepipelinecodecommitPipelineF780CA18EventRuleC207C969": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "source": [ + "aws.codecommit" + ], + "resources": [ + { + "Fn::GetAtt": [ + "MyRepoF4F48043", + "Arn" + ] + } + ], + "detail-type": [ + "CodeCommit Repository State Change" + ], + "detail": { + "event": [ + "referenceUpdated" + ], + "referenceName": [ + "master" + ] + } + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codepipeline:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "PipelineC660917D" + } + ] + ] + }, + "Id": "Pipeline", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineEventsRole46BEEA7C", + "Arn" + ] + } + } + ] + } + }, "PipelineArtifactsBucket22248F97": { "Type": "AWS::S3::Bucket", "DeletionPolicy": "Retain" @@ -126,7 +190,7 @@ ] }, "BranchName": "master", - "PollForSourceChanges": true + "PollForSourceChanges": false }, "InputArtifacts": [], "Name": "source", @@ -163,6 +227,66 @@ "PipelineRoleD68726F7", "PipelineRoleDefaultPolicyC7A05455" ] + }, + "PipelineEventsRole46BEEA7C": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineEventsRoleDefaultPolicyFF4FCCE0": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "codepipeline:StartPipelineExecution", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codepipeline:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "PipelineC660917D" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineEventsRoleDefaultPolicyFF4FCCE0", + "Roles": [ + { + "Ref": "PipelineEventsRole46BEEA7C" + } + ] + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.expected.json index e9e1f3be44016..47d2022609c36 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.expected.json @@ -238,7 +238,7 @@ ] } }, - "MyPipelineOnSourceStateChange7DC39EE9": { + "SourceOnSourceStateChangeEF8EB16D": { "Type": "AWS::Events::Rule", "Properties": { "EventPattern": { diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts index c27c392eb2e18..99d484f3658d6 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts @@ -23,6 +23,7 @@ const sourceAction = new codecommit.PipelineSourceAction(pipeline, 'CodeCommitSo stage: sourceStage, outputArtifactName: 'Source', repository, + pollForSourceChanges: true, }); new codebuild.PipelineBuildAction(stack, 'CodeBuildAction', { stage: buildStage, diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.cloudformation-pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline/test/test.cloudformation-pipeline-actions.ts index e6b66298cb0b9..214f63e90140f 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.cloudformation-pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.cloudformation-pipeline-actions.ts @@ -30,6 +30,7 @@ export = { stage: sourceStage, outputArtifactName: 'SourceArtifact', repository: repo, + pollForSourceChanges: true, }); /** Build! */ diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts index 67c41f552d255..6f93d3d0ef53d 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts @@ -334,30 +334,31 @@ export = { test.done(); }, - 'polling for changes': { - 'does not poll for changes'(test: Test) { + 'CodeCommit Action': { + 'does not poll for changes by default'(test: Test) { const stack = new cdk.Stack(); - - const result = new codecommit.PipelineSourceAction(stack, 'stage', { + const sourceAction = new codecommit.PipelineSourceAction(stack, 'stage', { stage: stageForTesting(stack), outputArtifactName: 'SomeArtifact', repository: repositoryForTesting(stack), - pollForSourceChanges: false, }); - test.equal(result.configuration.PollForSourceChanges, false); + + test.equal(sourceAction.configuration.PollForSourceChanges, false); + test.done(); }, - 'polls for changes'(test: Test) { + 'does not poll for source changes when explicitly set to false'(test: Test) { const stack = new cdk.Stack(); - - const result = new codecommit.PipelineSourceAction(stack, 'stage', { + const sourceAction = new codecommit.PipelineSourceAction(stack, 'stage', { stage: stageForTesting(stack), outputArtifactName: 'SomeArtifact', repository: repositoryForTesting(stack), - pollForSourceChanges: true, + pollForSourceChanges: false, }); - test.equal(result.configuration.PollForSourceChanges, true); + + test.equal(sourceAction.configuration.PollForSourceChanges, false); + test.done(); } } diff --git a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts index 70890d9a39504..1dea4cd5d5ed9 100644 --- a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts @@ -68,12 +68,12 @@ export class PipelineInvokeAction extends codepipeline.Action { }); // allow pipeline to list functions - props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement() + props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() .addAction('lambda:ListFunctions') .addAllResources()); // allow pipeline to invoke this lambda functionn - props.stage.pipelineRole.addToPolicy(new iam.PolicyStatement() + props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() .addAction('lambda:InvokeFunction') .addResource(props.lambda.functionArn)); diff --git a/packages/@aws-cdk/aws-s3/lib/pipeline-action.ts b/packages/@aws-cdk/aws-s3/lib/pipeline-action.ts index c77e805bcd98a..a4f03b9c79be5 100644 --- a/packages/@aws-cdk/aws-s3/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-s3/lib/pipeline-action.ts @@ -59,6 +59,6 @@ export class PipelineSourceAction extends codepipeline.SourceAction { }); // pipeline needs permissions to read from the S3 bucket - props.bucket.grantRead(props.stage.pipelineRole); + props.bucket.grantRead(props.stage.pipeline.role); } }