From c4054cefba812b1e79ed1b6ccb24b165daa2a642 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Mon, 9 Sep 2019 22:40:18 -0700 Subject: [PATCH] feat(events): allow passing a role to the CodePipeline target (#4006) Fixes #3999 --- .../aws-events-targets/lib/codepipeline.ts | 20 +- .../test/codepipeline/pipeline.test.ts | 171 +++++++++++------- 2 files changed, 123 insertions(+), 68 deletions(-) diff --git a/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts b/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts index d5e6204c57020..d46fd47de2c62 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts @@ -3,18 +3,32 @@ import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import { singletonEventRole } from './util'; +/** + * Customization options when creating a {@link CodePipeline} event target. + */ +export interface CodePipelineTargetOptions { /** - * Allows the pipeline to be used as a CloudWatch event rule target. + * The role to assume before invoking the target + * (i.e., the pipeline) when the given rule is triggered. + * + * @default - a new role will be created */ + readonly eventRole?: iam.IRole; +} + +/** + * Allows the pipeline to be used as a CloudWatch event rule target. + */ export class CodePipeline implements events.IRuleTarget { - constructor(private readonly pipeline: codepipeline.IPipeline) { + constructor(private readonly pipeline: codepipeline.IPipeline, + private readonly options: CodePipelineTargetOptions = {}) { } public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { return { id: '', arn: this.pipeline.pipelineArn, - role: singletonEventRole(this.pipeline, [new iam.PolicyStatement({ + role: this.options.eventRole || singletonEventRole(this.pipeline, [new iam.PolicyStatement({ resources: [this.pipeline.pipelineArn], actions: ['codepipeline:StartPipelineExecution'], })]), diff --git a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts index 62baaca9d2114..4fed87ae6b70b 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts @@ -1,79 +1,120 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import codepipeline = require('@aws-cdk/aws-codepipeline'); import events = require('@aws-cdk/aws-events'); -import { Construct, Stack } from '@aws-cdk/core'; +import iam = require('@aws-cdk/aws-iam'); +import { CfnElement, Construct, Stack } from '@aws-cdk/core'; import targets = require('../../lib'); -test('use codebuild project as an eventrule target', () => { - // GIVEN - const stack = new Stack(); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); +describe('CodePipeline event target', () => { + let stack: Stack; + let pipeline: codepipeline.Pipeline; + let pipelineArn: any; - const srcArtifact = new codepipeline.Artifact('Src'); - const buildArtifact = new codepipeline.Artifact('Bld'); - pipeline.addStage({ - stageName: 'Source', - actions: [new TestAction({ - actionName: 'Hello', - category: codepipeline.ActionCategory.SOURCE, - provider: 'x', - artifactBounds: { minInputs: 0, maxInputs: 0 , minOutputs: 1, maxOutputs: 1, }, - outputs: [srcArtifact]})] - }); - pipeline.addStage({ - stageName: 'Build', - actions: [new TestAction({ - actionName: 'Hello', - category: codepipeline.ActionCategory.BUILD, - provider: 'y', - inputs: [srcArtifact], - outputs: [buildArtifact], - artifactBounds: { minInputs: 1, maxInputs: 1 , minOutputs: 1, maxOutputs: 1, }})] + beforeEach(() => { + stack = new Stack(); + pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); + const srcArtifact = new codepipeline.Artifact('Src'); + const buildArtifact = new codepipeline.Artifact('Bld'); + pipeline.addStage({ + stageName: 'Source', + actions: [new TestAction({ + actionName: 'Hello', + category: codepipeline.ActionCategory.SOURCE, + provider: 'x', + artifactBounds: { minInputs: 0, maxInputs: 0 , minOutputs: 1, maxOutputs: 1, }, + outputs: [srcArtifact]})] + }); + pipeline.addStage({ + stageName: 'Build', + actions: [new TestAction({ + actionName: 'Hello', + category: codepipeline.ActionCategory.BUILD, + provider: 'y', + inputs: [srcArtifact], + outputs: [buildArtifact], + artifactBounds: { minInputs: 1, maxInputs: 1 , minOutputs: 1, maxOutputs: 1, }})] + }); + pipelineArn = { + "Fn::Join": [ "", [ + "arn:", + { Ref: "AWS::Partition" }, + ":codepipeline:", + { Ref: "AWS::Region" }, + ":", + { Ref: "AWS::AccountId" }, + ":", + { Ref: "PipelineC660917D" }] + ] + }; }); - const rule = new events.Rule(stack, 'rule', { - schedule: events.Schedule.expression('rate(1 minute)'), - }); + describe('when added to an event rule as a target', () => { + let rule: events.Rule; + + beforeEach(() => { + rule = new events.Rule(stack, 'rule', { + schedule: events.Schedule.expression('rate(1 minute)'), + }); + }); - // WHEN - rule.addTarget(new targets.CodePipeline(pipeline)); + describe('with default settings', () => { + beforeEach(() => { + rule.addTarget(new targets.CodePipeline(pipeline)); + }); - const pipelineArn = { - "Fn::Join": [ "", [ - "arn:", - { Ref: "AWS::Partition" }, - ":codepipeline:", - { Ref: "AWS::Region" }, - ":", - { Ref: "AWS::AccountId" }, - ":", - { Ref: "PipelineC660917D" }] - ] - }; + test("adds the pipeline's ARN and role to the targets of the rule", () => { + expect(stack).to(haveResource('AWS::Events::Rule', { + Targets: [ + { + Arn: pipelineArn, + Id: "Target0", + RoleArn: { "Fn::GetAtt": [ "PipelineEventsRole46BEEA7C", "Arn" ] }, + }, + ], + })); + }); - // THEN - expect(stack).to(haveResource('AWS::Events::Rule', { - Targets: [ - { - Arn: pipelineArn, - Id: "Target0", - RoleArn: { "Fn::GetAtt": [ "PipelineEventsRole46BEEA7C", "Arn" ] } - } - ] - })); + test("creates a policy that has StartPipeline permissions on the pipeline's ARN", () => { + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: "codepipeline:StartPipelineExecution", + Effect: "Allow", + Resource: pipelineArn, + } + ], + Version: "2012-10-17" + } + })); + }); + }); - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: "codepipeline:StartPipelineExecution", - Effect: "Allow", - Resource: pipelineArn, - } - ], - Version: "2012-10-17" - } - })); + describe('with an explicit event role', () => { + beforeEach(() => { + const role = new iam.Role(stack, 'MyExampleRole', { + assumedBy: new iam.AnyPrincipal(), + }); + const roleResource = role.node.defaultChild as CfnElement; + roleResource.overrideLogicalId('MyRole'); // to make it deterministic in the assertion below + + rule.addTarget(new targets.CodePipeline(pipeline, { + eventRole: role, + })); + }); + + test("points at the given event role in the rule's targets", () => { + expect(stack).to(haveResourceLike('AWS::Events::Rule', { + Targets: [ + { + Arn: pipelineArn, + RoleArn: { "Fn::GetAtt": ["MyRole", "Arn"] }, + }, + ], + })); + }); + }); + }); }); class TestAction implements codepipeline.IAction {