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..b6bbeb1fcdfb5 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'); @@ -295,6 +296,14 @@ class StageDouble implements cpapi.IStage, cpapi.IInternalStage { throw new Error('Unsupported'); } + public get pipelineUniqueId(): string { + throw new Error('Unsupported'); + } + + public get pipelineAsEventTarget(): events.IEventRuleTarget { + throw new Error('Unsupported'); + } + public _attachAction(action: cpapi.Action) { this.actions.push(action); } diff --git a/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts index 87027af072e1e..7340aa94ec02b 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.pipelineUniqueId + 'EventRule', props.stage.pipelineAsEventTarget, props.branch || 'master'); + } + // https://docs.aws.amazon.com/codecommit/latest/userguide/auth-and-access-control-permissions-reference.html#aa-acp const actions = [ 'codecommit:GetBranch', diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts index c43d013e4ac3e..9213f8d79629e 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts @@ -81,11 +81,21 @@ export interface IStage { */ readonly pipelineArn: string; + /** + * The unique ID of the Pipeline Construct. + */ + readonly pipelineUniqueId: string; + /** * The service Role of the Pipeline. */ readonly pipelineRole: iam.Role; + /** + * Allows accessing the Pipeline as the Target for CloudWatch Events. + */ + readonly pipelineAsEventTarget: events.IEventRuleTarget; + /** * The API of Stage used internally by the CodePipeline Construct. * You should never need to call any of the methods inside of it yourself. diff --git a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts index 05e4df56f81cd..172076666b8ae 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts @@ -143,10 +143,18 @@ export class Stage extends cdk.Construct implements actions.IStage, actions.IInt return this.pipeline.pipelineArn; } + public get pipelineUniqueId(): string { + return this.pipeline.uniqueId; + } + public get pipelineRole(): iam.Role { return this.pipeline.role; } + public get pipelineAsEventTarget(): events.IEventRuleTarget { + return this.pipeline; + } + // can't make this method private like Pipeline#_attachStage, // as it comes from the IStage interface public _attachAction(action: actions.Action): void { 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.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.action.ts b/packages/@aws-cdk/aws-codepipeline/test/test.action.ts index 0af650baf9d41..f1874a0561ab6 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.action.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.action.ts @@ -117,6 +117,56 @@ export = { test.done(); }, + + 'CodeCommit Action': { + 'does not poll for source changes by default'(test: Test) { + const stack = new cdk.Stack(); + const pipeline = new codepipeline.Pipeline(stack, 'pipeline'); + const stage = pipeline.addStage('Source'); + const repository = new codecommit.Repository(stack, 'Repsotiory', { repositoryName: 'Repository' }); + repository.addToPipeline(stage, 'CodeCommit'); + + expect(stack, true).to(haveResource('AWS::CodePipeline::Pipeline', { + "Stages": [ + { + "Actions": [ + { + "Configuration": { + "PollForSourceChanges": false, + }, + }, + ], + }, + ], + })); + + test.done(); + }, + + 'does not poll for source changes when explicitly set to false'(test: Test) { + const stack = new cdk.Stack(); + const pipeline = new codepipeline.Pipeline(stack, 'pipeline'); + const stage = pipeline.addStage('Source'); + const repository = new codecommit.Repository(stack, 'Repsotiory', { repositoryName: 'Repository' }); + repository.addToPipeline(stage, 'CodeCommit', { pollForSourceChanges: false }); + + expect(stack, true).to(haveResource('AWS::CodePipeline::Pipeline', { + "Stages": [ + { + "Actions": [ + { + "Configuration": { + "PollForSourceChanges": false, + }, + }, + ], + }, + ], + })); + + test.done(); + }, + }, }; function boundsValidationResult(numberOfArtifacts: number, min: number, max: number): string[] { 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! */