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',