From ca8a3057c4b3875a0473555b2c77ee1ec98a8a4e Mon Sep 17 00:00:00 2001 From: Rado Smogura Date: Mon, 17 Dec 2018 05:39:52 -0800 Subject: [PATCH] =?UTF-8?q?feat(aws-codepipeline):=20support=20for=20pipel?= =?UTF-8?q?ine=20action=E2=80=99s=20service=20role?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In realation to https://github.com/awslabs/aws-cdk/issues/49 The action’s service roles is a role which will be assumed by pipeline during execution of this action. The pipeline action’s service role can be used to perform more advanced configuration, when i.e. elevation of permissions is required, or when fine grained access control may be required. https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages-actions.html This commit is motivated by enabling cross-account deployments, for which service role will be used as jump role to assume one used by Cloud Formation in target account. --- .gitignore | 2 + .../lib/pipeline-actions.ts | 3 +- .../aws-codecommit/lib/pipeline-action.ts | 3 +- .../aws-codecommit/test/test.codecommit.ts | 4 +- .../aws-codedeploy/lib/pipeline-action.ts | 3 +- .../test/test.pipeline-action.ts | 66 ++++ .../aws-codepipeline-api/lib/action.ts | 20 ++ .../lib/github-source-action.ts | 3 +- .../@aws-cdk/aws-codepipeline/lib/stage.ts | 1 + ...ipeline-cfn-wtih-action-role.expected.json | 317 ++++++++++++++++++ .../integ.pipeline-cfn-wtih-action-role.ts | 48 +++ .../test.cloudformation-pipeline-actions.ts | 51 ++- .../aws-codepipeline/test/test.pipeline.ts | 19 +- .../aws-lambda/lib/pipeline-action.ts | 1 + 14 files changed, 527 insertions(+), 14 deletions(-) create mode 100644 packages/@aws-cdk/aws-codedeploy/test/test.pipeline-action.ts create mode 100644 packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.expected.json create mode 100644 packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.ts diff --git a/.gitignore b/.gitignore index 7b2c8a301c245..9b8a449c7187b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .vscode +# VSCode extension +/.favorites.json .DS_Store node_modules lerna-debug.log diff --git a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts index 1c3ee7be6767f..f32eba5876b42 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts @@ -59,8 +59,7 @@ export abstract class PipelineCloudFormationAction extends codepipeline.Action { constructor(scope: cdk.Construct, id: string, props: PipelineCloudFormationActionProps, configuration?: any) { super(scope, id, { - stage: props.stage, - runOrder: props.runOrder, + ...props, region: props.region, artifactBounds: { minInputs: 0, diff --git a/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts index 596a7d7882cad..68d0444565b5b 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts @@ -48,8 +48,7 @@ export interface PipelineSourceActionProps extends CommonPipelineSourceActionPro export class PipelineSourceAction extends codepipeline.SourceAction { constructor(scope: cdk.Construct, id: string, props: PipelineSourceActionProps) { super(scope, id, { - stage: props.stage, - runOrder: props.runOrder, + ...props, provider: 'CodeCommit', configuration: { RepositoryName: props.repository.repositoryName, diff --git a/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts b/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts index ef60996354dd3..d160d8d583eee 100644 --- a/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts +++ b/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts @@ -1,6 +1,6 @@ import { App, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { Repository, RepositoryProps } from '../lib'; +import { Repository, RepositoryProps } from '../lib'; export = { 'default properties': { @@ -49,7 +49,7 @@ export = { test.throws(() => myRepository.notify('myTrigger')); test.done(); - } + }, } }; diff --git a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts index 27bd3485d3ba0..70f44bbb87df0 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts @@ -31,8 +31,7 @@ export interface PipelineDeployActionProps extends CommonPipelineDeployActionPro export class PipelineDeployAction extends codepipeline.DeployAction { constructor(scope: cdk.Construct, id: string, props: PipelineDeployActionProps) { super(scope, id, { - stage: props.stage, - runOrder: props.runOrder, + ...props, artifactBounds: { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 }, provider: 'CodeDeploy', inputArtifact: props.inputArtifact, diff --git a/packages/@aws-cdk/aws-codedeploy/test/test.pipeline-action.ts b/packages/@aws-cdk/aws-codedeploy/test/test.pipeline-action.ts new file mode 100644 index 0000000000000..67e1faba2b4f5 --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/test/test.pipeline-action.ts @@ -0,0 +1,66 @@ +import { IPipeline } from '@aws-cdk/aws-codepipeline-api'; +import * as iam from '@aws-cdk/aws-iam'; +import { App, Stack } from '@aws-cdk/cdk'; +import { Test } from 'nodeunit'; +import { Bucket, PipelineSourceAction } from '../../aws-s3/lib'; +import { PipelineDeployAction, ServerDeploymentGroup } from '../lib'; + +export = { + 'test passing properties to action'(test: Test) { + const app = new TestApp(); + + const source = new PipelineSourceAction(app.stack, 'Src', { + bucket: Bucket.import(app.stack, 'Bucket', {bucketName: 'someName'}), + bucketKey: 'bkkey', + stage: { + node: app.stack.node, + name: 'Source', + pipeline: { + role: iam.Role.import(app.stack, 'r1', { roleArn: 'arn:aws:iam::123456789012:role/superUser1'} ) + } as IPipeline, + _internal: { + _attachAction: () => null, + _findInputArtifact: () => { throw new Error('X'); }, + _generateOutputArtifactName: () => 'SourceOut' + } + } + }); + + const action = new PipelineDeployAction(app.stack, 'Id', { + runOrder: 456, + stage: { + node: app.stack.node, + name: 'Source', + pipeline: { + role: iam.Role.import(app.stack, 'r2', { roleArn: 'arn:aws:iam::123456789012:role/superUser2'} ) + } as IPipeline, + _internal: { + _attachAction: () => null, + _findInputArtifact: () => { throw new Error('X'); }, + _generateOutputArtifactName: () => 'DeployOut' + } + }, + actionRole: iam.Role.import(app.stack, 'Role', { + roleArn: 'arn:aws:iam::123456789012:role/superUser' + }), + deploymentGroup: new ServerDeploymentGroup(app.stack, 'DeployGroup', { + deploymentGroupName: 'DGName' + }), + inputArtifact: source.outputArtifact + }); + + test.equals(action.runOrder, 456); + test.equals(action.actionRole!.roleArn, 'arn:aws:iam::123456789012:role/superUser'); + test.done(); + } +}; + +class TestApp { + private readonly app = new App(); + // tslint:disable-next-line:member-ordering + public readonly stack: Stack = new Stack(this.app, 'MyStack'); + + public synthesizeTemplate() { + return this.app.synthesizeStack(this.stack.name).template; + } +} diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts index 0e1bbf2344590..7bb6e2455242b 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts @@ -136,6 +136,15 @@ export interface CommonActionProps { * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html */ runOrder?: number; + + /** + * The service role that is assumed during execution of action. + * This role is not mandatory, however more advanced configuration + * may require specifying it. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages-actions.html + */ + actionRole?: iam.IRole; } /** @@ -205,6 +214,15 @@ export abstract class Action extends cdk.Construct { */ public readonly configuration?: any; + /** + * The service role that is assumed during execution of action. + * This role is not mandatory, however more advanced configuration + * may require specifying it. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages-actions.html + */ + public readonly actionRole?: iam.IRole; + /** * The order in which AWS CodePipeline runs this action. * For more information, see the AWS CodePipeline User Guide. @@ -218,6 +236,7 @@ export abstract class Action extends cdk.Construct { private readonly _actionInputArtifacts = new Array(); private readonly _actionOutputArtifacts = new Array(); + private readonly artifactBounds: ActionArtifactBounds; private readonly stage: IStage; @@ -235,6 +254,7 @@ export abstract class Action extends cdk.Construct { this.artifactBounds = props.artifactBounds; this.runOrder = props.runOrder === undefined ? 1 : props.runOrder; this.stage = props.stage; + this.actionRole = props.actionRole; this.stage._internal._attachAction(this); } diff --git a/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts b/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts index c70c2d048ddf7..841ca8da61154 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts @@ -58,8 +58,7 @@ export interface GitHubSourceActionProps extends actions.CommonActionProps, export class GitHubSourceAction extends actions.SourceAction { constructor(scope: cdk.Construct, id: string, props: GitHubSourceActionProps) { super(scope, id, { - stage: props.stage, - runOrder: props.runOrder, + ...props, owner: 'ThirdParty', provider: 'GitHub', configuration: { diff --git a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts index 1a1eb46e6e4ea..46c6ff967ff9f 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts @@ -162,6 +162,7 @@ export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInterna configuration: action.configuration, outputArtifacts: action._outputArtifacts.map(a => ({ name: a.name })), runOrder: action.runOrder, + roleArn: action.actionRole ? action.actionRole.roleArn : undefined }; } diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.expected.json new file mode 100644 index 0000000000000..80f387afaf375 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.expected.json @@ -0,0 +1,317 @@ +{ + "Resources": { + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "Properties": { + "VersioningConfiguration": { + "Status": "Enabled" + } + } + }, + "MyPipelineRoleC0D47CA4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyPipelineRoleDefaultPolicy34F09EFA": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "CFNDeployRole68D5E8D3", + "Arn" + ] + } + }, + { + "Action": [ + "cloudformation:CreateStack", + "cloudformation:DescribeStack*", + "cloudformation:GetStackPolicy", + "cloudformation:GetTemplate*", + "cloudformation:SetStackPolicy", + "cloudformation:UpdateStack", + "cloudformation:ValidateTemplate" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":cloudformation:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":stack/aws-cdk-codepipeline-cross-region-deploy-stack/*" + ] + ] + } + }, + { + "Action": [ + "sts:AssumeRole", + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyPipelineRoleDefaultPolicy34F09EFA", + "Roles": [ + { + "Ref": "MyPipelineRoleC0D47CA4" + } + ] + } + }, + "MyPipelineAED38ECF": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "MyPipelineRoleC0D47CA4", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1" + }, + "Configuration": { + "S3Bucket": { + "Ref": "MyBucketF68F3FF0" + }, + "S3ObjectKey": "some/path", + "PollForSourceChanges": true + }, + "InputArtifacts": [], + "Name": "S3", + "OutputArtifacts": [ + { + "Name": "Artifact_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketS30423514B" + } + ], + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "StackName": "aws-cdk-codepipeline-cross-region-deploy-stack", + "ActionMode": "CREATE_UPDATE", + "TemplatePath": "Artifact_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketS30423514B::template.yml", + "RoleArn": { + "Fn::GetAtt": [ + "CFNDeployRole68D5E8D3", + "Arn" + ] + } + }, + "InputArtifacts": [ + { + "Name": "Artifact_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketS30423514B" + } + ], + "Name": "CFN_Deploy", + "OutputArtifacts": [], + "RoleArn": { + "Fn::GetAtt": [ + "ActionRole60B0EDF7", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "CFN" + } + ], + "ArtifactStore": { + "Location": { + "Ref": "MyBucketF68F3FF0" + }, + "Type": "S3" + } + }, + "DependsOn": [ + "MyPipelineRoleC0D47CA4", + "MyPipelineRoleDefaultPolicy34F09EFA" + ] + }, + "ActionRole60B0EDF7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ActionRoleDefaultPolicyCA33BE56": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ActionRoleDefaultPolicyCA33BE56", + "Roles": [ + { + "Ref": "ActionRole60B0EDF7" + } + ] + } + }, + "CFNDeployRole68D5E8D3": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "cloudformation.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.ts new file mode 100644 index 0000000000000..4de4fb1c1578b --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.ts @@ -0,0 +1,48 @@ +import cloudformation = require('@aws-cdk/aws-cloudformation'); +import iam = require('@aws-cdk/aws-iam'); +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import codepipeline = require('../lib'); + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-cloudformation-cross-region-with-action-role', {}); + +const bucket = new s3.Bucket(stack, 'MyBucket', { + versioned: true, + removalPolicy: cdk.RemovalPolicy.Destroy, +}); + +const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline', { + artifactBucket: bucket, +}); + +const sourceStage = pipeline.addStage('Source'); +const sourceAction = bucket.addToPipeline(sourceStage, 'S3', { + bucketKey: 'some/path', +}); + +const cfnStage = pipeline.addStage('CFN'); + +const role = new iam.Role(stack, 'ActionRole', { + assumedBy: new iam.AccountPrincipal(new cdk.Aws().accountId) +}); +role.addToPolicy(new iam.PolicyStatement() + .addAction('sqs:*') + .addAllResources() +); + +new cloudformation.PipelineCreateUpdateStackAction(stack, 'CFN_Deploy', { + stage: cfnStage, + stackName: 'aws-cdk-codepipeline-cross-region-deploy-stack', + templatePath: sourceAction.outputArtifact.atPath('template.yml'), + adminPermissions: false, + actionRole: role +}); + +pipeline.addToRolePolicy(new iam.PolicyStatement() + .addActions("sts:AssumeRole", "iam:PassRole") + .addAllResources() +); + +app.run(); 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 a33581084b12a..8abe7f5f7a46b 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 @@ -62,7 +62,9 @@ export = { stage: prodStage, stackName, changeSetName, + runOrder: 321, role: changeSetExecRole, + deploymentRole: changeSetExecRole, templatePath: new ArtifactPath(buildAction.outputArtifact, 'template.yaml'), templateConfiguration: new ArtifactPath(buildAction.outputArtifact, 'templateConfig.json'), adminPermissions: false, @@ -169,7 +171,7 @@ export = { "InputArtifacts": [{"Name": "OutputYo"}], "Name": "BuildChangeSetProd", "OutputArtifacts": [], - "RunOrder": 1 + "RunOrder": 321 }, { "ActionTypeId": { @@ -356,6 +358,53 @@ export = { })); test.done(); + }, + + 'Action service role is passed to template'(test: Test) { + const stack = new TestFixture(); + + const importedRole = Role.import(stack, 'ImportedRole', { + roleArn: 'arn:aws:iam::000000000000:role/action-role' + }); + const freshRole = new Role(stack, 'FreshRole', { + assumedBy: new ServicePrincipal('magicservice') + }); + + new PipelineExecuteChangeSetAction(stack.pipeline, 'ImportedRoleAction', { + actionRole: importedRole, + changeSetName: 'magicSet', + stackName: 'magicStack', + stage: stack.deployStage + }); + + new PipelineExecuteChangeSetAction(stack.pipeline, 'FreshRoleAction', { + actionRole: freshRole, + changeSetName: 'magicSet', + stackName: 'magicStack', + stage: stack.deployStage + }); + + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + "Stages": [{ + "Name": "Source" /* don't care about the rest */ + }, { + "Name": "Deploy", + "Actions": [{ + "Name": "ImportedRoleAction", + "RoleArn": "arn:aws:iam::000000000000:role/action-role" + }, { + "Name": "FreshRoleAction", + "RoleArn": { + "Fn::GetAtt": [ + "FreshRole472F6E18", + "Arn" + ] + } + }] + }] + })); + + test.done(); } }; diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts index de1b69b01c5be..6be66edea0fa2 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts @@ -2,6 +2,7 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import cloudformation = require('@aws-cdk/aws-cloudformation'); import codebuild = require('@aws-cdk/aws-codebuild'); import codecommit = require('@aws-cdk/aws-codecommit'); +import iam = require( '@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); import s3 = require('@aws-cdk/aws-s3'); import sns = require('@aws-cdk/aws-sns'); @@ -49,6 +50,10 @@ export = { const p = new codepipeline.Pipeline(stack, 'P'); + const role = iam.Role.import(stack, 'PipelineActionRole', { + roleArn: 'arn:aws:iam::123456789012:root' + }); + const s1 = new codepipeline.Stage(stack, 'Source', { pipeline: p }); new codepipeline.GitHubSourceAction(stack, 'GH', { stage: s1, @@ -57,7 +62,8 @@ export = { branch: 'branch', oauthToken: secret.value, owner: 'foo', - repo: 'bar' + repo: 'bar', + actionRole: role }); const s2 = new codepipeline.Stage(stack, 'Two', { pipeline: p }); @@ -102,7 +108,8 @@ export = { "Name": "A" } ], - "RunOrder": 8 + "RunOrder": 8, + "RoleArn": "arn:aws:iam::123456789012:root" } ], "Name": "Source" @@ -288,11 +295,16 @@ export = { outputArtifactName: 'sourceArtifact2', }); + const role = iam.Role.import(stack, 'PipelineActionRole', { + roleArn: 'arn:aws:iam::123456789012:root' + }); + const stage = new codepipeline.Stage(stack, 'Stage', { pipeline }); const lambdaAction = new lambda.PipelineInvokeAction(stack, 'InvokeAction', { stage, lambda: lambdaFun, userParameters: 'foo-bar/42', + actionRole: role, inputArtifacts: [ source2.outputArtifact, source1.outputArtifact, @@ -346,6 +358,7 @@ export = { { "Name": "lambdaOutput2" }, { "Name": "lambdaOutput3" }, ], + "RoleArn": "arn:aws:iam::123456789012:root", "RunOrder": 1 } ], @@ -518,7 +531,7 @@ export = { test.done(); }, }, -}; +} as any; function stageForTesting(stack: cdk.Stack): codepipeline.Stage { const pipeline = new codepipeline.Pipeline(stack, 'pipeline'); diff --git a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts index ac14ea505feb0..d8f7c21e7f5c6 100644 --- a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts @@ -84,6 +84,7 @@ export class PipelineInvokeAction extends codepipeline.Action { constructor(scope: cdk.Construct, id: string, props: PipelineInvokeActionProps) { super(scope, id, { stage: props.stage, + actionRole: props.actionRole, runOrder: props.runOrder, category: codepipeline.ActionCategory.Invoke, provider: 'Lambda',