Skip to content

Commit

Permalink
feat(events): allow passing a role to the CodePipeline target (#4006)
Browse files Browse the repository at this point in the history
Fixes #3999
  • Loading branch information
skinny85 authored and mergify[bot] committed Sep 10, 2019
1 parent 181b58b commit c4054ce
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 68 deletions.
20 changes: 17 additions & 3 deletions packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
})]),
Expand Down
171 changes: 106 additions & 65 deletions packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down

0 comments on commit c4054ce

Please sign in to comment.