From 77fe077627c812ed62914952fc69decc0306eec7 Mon Sep 17 00:00:00 2001 From: Radek Date: Thu, 24 Jan 2019 13:57:09 -0800 Subject: [PATCH] feat(aws-codepipeline): support setting a Role for a CFN Action (#1449) This adds the option to set a Role for a CodePipeline Action. As of now, this is limited to only the CloudFormation Pipeline Actions. Setting the Role is needed for some more advanced setups, for example cross-account deployments. BREAKING CHANGE: the `role` property in the CloudFormation Actions has been renamed to `deploymentRole`. BREAKING CHANGE: the `role` property in the `app-delivery` package has been renamed to `deploymentRole`. --- .gitignore | 2 + .../lib/pipeline-deploy-stack-action.ts | 10 +- .../test/test.pipeline-deploy-stack-action.ts | 4 +- .../lib/pipeline-actions.ts | 32 +- .../test/test.pipeline-actions.ts | 6 +- .../aws-codecommit/lib/pipeline-action.ts | 3 +- .../aws-codedeploy/lib/pipeline-action.ts | 3 +- .../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/integ.pipeline-cfn.ts | 2 +- .../test.cloudformation-pipeline-actions.ts | 55 ++- .../aws-lambda/lib/pipeline-action.ts | 3 +- 15 files changed, 477 insertions(+), 32 deletions(-) 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/app-delivery/lib/pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts index 4b9873f6b48b2..e5fd317df35a6 100644 --- a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts @@ -97,7 +97,7 @@ export class PipelineDeployStackAction extends cdk.Construct { /** * The role used by CloudFormation for the deploy action */ - public readonly role: iam.IRole; + public readonly deploymentRole: iam.IRole; private readonly stack: cdk.Stack; @@ -127,10 +127,10 @@ export class PipelineDeployStackAction extends cdk.Construct { stage: props.stage, templatePath: props.inputArtifact.atPath(`${props.stack.name}.template.yaml`), adminPermissions: props.adminPermissions, - role: props.role, + deploymentRole: props.role, capabilities, }); - this.role = changeSetAction.role; + this.deploymentRole = changeSetAction.deploymentRole; new cfn.PipelineExecuteChangeSetAction(this, 'Execute', { changeSetName, @@ -149,8 +149,8 @@ export class PipelineDeployStackAction extends cdk.Construct { * `adminPermissions` you need to identify the proper statements to add to * this role based on the CloudFormation Resources in your stack. */ - public addToRolePolicy(statement: iam.PolicyStatement) { - this.role.addToPolicy(statement); + public addToDeploymentRolePolicy(statement: iam.PolicyStatement) { + this.deploymentRole.addToPolicy(statement); } protected validate(): string[] { diff --git a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts index 25f41261ec849..63a6a9089457b 100644 --- a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts @@ -188,7 +188,7 @@ export = nodeunit.testCase({ adminPermissions: false, role }); - test.same(deployAction.role, role); + test.same(deployAction.deploymentRole, role); test.done(); }, 'users can specify IAM permissions for the deploy action'(test: nodeunit.Test) { @@ -211,7 +211,7 @@ export = nodeunit.testCase({ adminPermissions: false, }); // we might need to add permissions - deployAction.addToRolePolicy( new iam.PolicyStatement(). + deployAction.addToDeploymentRolePolicy( new iam.PolicyStatement(). addActions( 'ec2:AuthorizeSecurityGroupEgress', 'ec2:AuthorizeSecurityGroupIngress', diff --git a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts index 1c3ee7be6767f..5c690a9ceee62 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts @@ -44,6 +44,15 @@ export interface PipelineCloudFormationActionProps extends codepipeline.CommonAc * @default the Action resides in the same region as the Pipeline */ region?: string; + + /** + * 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 + */ + role?: iam.IRole; } /** @@ -59,8 +68,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, @@ -123,7 +131,7 @@ export interface PipelineCloudFormationDeployActionProps extends PipelineCloudFo * * @default A fresh role with full or no permissions (depending on the value of `adminPermissions`). */ - role?: iam.IRole; + deploymentRole?: iam.IRole; /** * Acknowledge certain changes made as part of deployment @@ -194,7 +202,7 @@ export interface PipelineCloudFormationDeployActionProps extends PipelineCloudFo * Base class for all CloudFormation actions that execute or stage deployments. */ export abstract class PipelineCloudFormationDeployAction extends PipelineCloudFormationAction { - public readonly role: iam.IRole; + public readonly deploymentRole: iam.IRole; constructor(scope: cdk.Construct, id: string, props: PipelineCloudFormationDeployActionProps, configuration: any) { const capabilities = props.adminPermissions && props.capabilities === undefined ? CloudFormationCapabilities.NamedIAM : props.capabilities; @@ -202,32 +210,32 @@ export abstract class PipelineCloudFormationDeployAction extends PipelineCloudFo ...configuration, // None evaluates to empty string which is falsey and results in undefined Capabilities: (capabilities && capabilities.toString()) || undefined, - RoleArn: new cdk.Token(() => this.role.roleArn), + RoleArn: new cdk.Token(() => this.deploymentRole.roleArn), ParameterOverrides: new cdk.Token(() => this.node.stringifyJson(props.parameterOverrides)), TemplateConfiguration: props.templateConfiguration ? props.templateConfiguration.location : undefined, StackName: props.stackName, }); - if (props.role) { - this.role = props.role; + if (props.deploymentRole) { + this.deploymentRole = props.deploymentRole; } else { - this.role = new iam.Role(this, 'Role', { + this.deploymentRole = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('cloudformation.amazonaws.com') }); if (props.adminPermissions) { - this.role.addToPolicy(new iam.PolicyStatement().addAction('*').addAllResources()); + this.deploymentRole.addToPolicy(new iam.PolicyStatement().addAction('*').addAllResources()); } } - SingletonPolicy.forRole(props.stage.pipeline.role).grantPassRole(this.role); + SingletonPolicy.forRole(props.stage.pipeline.role).grantPassRole(this.deploymentRole); } /** * Add statement to the service role assumed by CloudFormation while executing this action. */ - public addToRolePolicy(statement: iam.PolicyStatement) { - return this.role.addToPolicy(statement); + public addToDeploymentRolePolicy(statement: iam.PolicyStatement) { + return this.deploymentRole.addToPolicy(statement); } } 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 2e512336c0ab5..65820d877ff1b 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts @@ -21,7 +21,7 @@ export = nodeunit.testCase({ adminPermissions: false, }); - _assertPermissionGranted(test, pipelineRole.statements, 'iam:PassRole', action.role.roleArn); + _assertPermissionGranted(test, pipelineRole.statements, 'iam:PassRole', action.deploymentRole.roleArn); const stackArn = _stackArn('MyStack', stack); const changeSetCondition = { StringEqualsIfExists: { 'cloudformation:ChangeSetName': 'MyChangeSet' } }; @@ -175,7 +175,7 @@ export = nodeunit.testCase({ _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:UpdateStack', stackArn); _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:DeleteStack', stackArn); - _assertPermissionGranted(test, pipelineRole.statements, 'iam:PassRole', action.role.roleArn); + _assertPermissionGranted(test, pipelineRole.statements, 'iam:PassRole', action.deploymentRole.roleArn); test.done(); }, @@ -193,7 +193,7 @@ export = nodeunit.testCase({ _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:DescribeStack*', stackArn); _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:DeleteStack', stackArn); - _assertPermissionGranted(test, pipelineRole.statements, 'iam:PassRole', action.role.roleArn); + _assertPermissionGranted(test, pipelineRole.statements, 'iam:PassRole', action.deploymentRole.roleArn); test.done(); }, 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-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-codepipeline-api/lib/action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts index 0e1bbf2344590..6d44b0c146f96 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts @@ -162,6 +162,15 @@ export interface ActionProps extends CommonActionProps, CommonActionConstructPro */ region?: string; + /** + * 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 + */ + role?: iam.IRole; + artifactBounds: ActionArtifactBounds; configuration?: any; version?: string; @@ -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 role?: 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.role = props.role; 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..69a2e8757006b 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.role ? action.role.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..7d66c3d73bdc3 --- /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, + role +}); + +pipeline.addToRolePolicy(new iam.PolicyStatement() + .addActions("sts:AssumeRole", "iam:PassRole") + .addAllResources() +); + +app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts index f34160c9f40b6..ffff4d7b54789 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts @@ -36,7 +36,7 @@ new cfn.PipelineCreateReplaceChangeSetAction(stack, 'DeployCFN', { stage: cfnStage, changeSetName, stackName, - role, + deploymentRole: role, templatePath: source.outputArtifact.atPath('test.yaml'), adminPermissions: false, parameterOverrides: { 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..701628e188b9c 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,7 @@ export = { stage: prodStage, stackName, changeSetName, - role: changeSetExecRole, + deploymentRole: changeSetExecRole, templatePath: new ArtifactPath(buildAction.outputArtifact, 'template.yaml'), templateConfiguration: new ArtifactPath(buildAction.outputArtifact, 'templateConfig.json'), adminPermissions: false, @@ -356,6 +356,59 @@ 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', { + role: importedRole, + changeSetName: 'magicSet', + stackName: 'magicStack', + stage: stack.deployStage + }); + + new PipelineExecuteChangeSetAction(stack.pipeline, 'FreshRoleAction', { + role: 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-lambda/lib/pipeline-action.ts b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts index ac14ea505feb0..562d7238e38e4 100644 --- a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts @@ -83,8 +83,7 @@ export interface PipelineInvokeActionProps extends CommonPipelineInvokeActionPro export class PipelineInvokeAction extends codepipeline.Action { constructor(scope: cdk.Construct, id: string, props: PipelineInvokeActionProps) { super(scope, id, { - stage: props.stage, - runOrder: props.runOrder, + ...props, category: codepipeline.ActionCategory.Invoke, provider: 'Lambda', artifactBounds: codepipeline.defaultBounds(),