From 4360d285b326ae0db1ec74ae53cb874da6d28ead Mon Sep 17 00:00:00 2001 From: Madhumitran Loganathan Date: Tue, 7 Jul 2020 14:58:05 -0700 Subject: [PATCH 1/7] feat: allow stepfunctions invoke action this feature enables developers using the cdk to create stepfunction invoke action in their stacks. verified the changes using unit tests. Reviewed by: sainsbm --- .../aws-codepipeline-actions/lib/index.ts | 1 + .../lib/stepfunctions/invoke-action.ts | 149 +++++++++++++++ .../aws-codepipeline-actions/package.json | 2 + .../test.stepfunctions-invoke-actions.ts | 175 ++++++++++++++++++ 4 files changed, 327 insertions(+) create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts index b57acaec03b0d..74d27362c5a3b 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts @@ -14,4 +14,5 @@ export * from './lambda/invoke-action'; export * from './manual-approval-action'; export * from './s3/deploy-action'; export * from './s3/source-action'; +export * from './stepfunctions/invoke-action'; export * from './action'; // for some reason, JSII fails building the module without exporting this class diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts new file mode 100644 index 0000000000000..25b7b04c1c37a --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts @@ -0,0 +1,149 @@ +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as iam from '@aws-cdk/aws-iam'; +import * as stepfunction from '@aws-cdk/aws-stepfunctions'; +import { Construct} from '@aws-cdk/core'; +import { Action } from '../action'; + +/** + * Represents the input type for the StateMachine. + */ +export enum StateMachineInputType { + /** + * When specified, the value in the Input field is passed + * directly to the state machine input. + */ + LITERAL = 'Literal', + + /** + * The contents of a file in the input artifact specified by the Input field + * is used as the input for the state machine execution. + * Input artifact and FilePath is required when InputType is set to FilePath. + */ + FILEPATH = 'FilePath', +} + +/** + * Construction properties of the {@link StepFunctionsInvokeAction StepFunction Invoke Action}. + */ +export interface StepFunctionsInvokeActionProps extends codepipeline.CommonAwsActionProps { + /** + * The optional input Artifact of the Action. + * If InputType is set to FilePath, this artifact is required + * and is used to source the input for the state machine execution. + * + * @default the Action will not have any inputs + * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-StepFunctions.html#action-reference-StepFunctions-example + */ + readonly input?: codepipeline.Artifact; + + /** + * The optional output Artifact of the Action. + * + * @default the Action will not have any outputs + */ + readonly output?: codepipeline.Artifact; + + /** + * The state machine to invoke. + */ + readonly stateMachine: stepfunction.IStateMachine; + + /** + * Optional StateMachine InputType + * InputType can be Literal or FilePath + * + * @default - Literal + */ + readonly stateMachineInputType?: StateMachineInputType; + + /**y + * When InputType is set to Literal (default), this field is optional. + * If provided, the Input field is used directly as the input for the state machine execution. + * Otherwise, the state machine is invoked with an empty JSON object {}. + * + * When InputType is set to FilePath, this field is required. + * An input artifact is also required when InputType is set to FilePath. + * + * @default - none + */ + readonly stateMachineInput?: string | object; + + + /** + * Prefix (optinoal) + * + * By default, the action execution ID is used as the state machine execution name. + * If a prefix is provided, it is prepended to the action execution ID with a hyphen and + * together used as the state machine execution name. + */ + readonly executionPrefixName?: string; +} + +/** + * CodePipeline invoke Action that is provided by an AWS Step Function. + */ +export class StepFunctionsInvokeAction extends Action { + private readonly props: StepFunctionsInvokeActionProps; + + constructor(props: StepFunctionsInvokeActionProps) { + super({ + ...props, + resource: props.stateMachine, + category: codepipeline.ActionCategory.INVOKE, + provider: 'StepFunctions', + artifactBounds: { + minInputs: 0, + maxInputs: 1, + minOutputs: 0, + maxOutputs: 1, + }, + }); + + //StateMachineInput is required when the StateMachineInputType is set as FilePath + if ( props.stateMachineInputType == StateMachineInputType.FILEPATH && (!props.stateMachineInput)) { + throw new Error('Input type FilePath was specified, but no filepath was provided'); + } + + //Input Artifact is required when the StateMachineInputType is set as FilePath + if ( props.stateMachineInputType == StateMachineInputType.FILEPATH && (!this.actionProperties.inputs)) { + throw new Error('Input type FilePath was specified, but no input artifact was provided'); + } + + this.props = props; + } + + protected bound(_scope: Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): + codepipeline.ActionConfig { + // allow pipeline to invoke this step function + options.role.addToPolicy(new iam.PolicyStatement({ + actions: ['states:StartExecution','states:DescribeStateMachine'], + resources: [this.props.stateMachine.stateMachineArn], + })); + + // allow action executions to be inspected and invoked + options.role.addToPolicy(new iam.PolicyStatement({ + actions: ['states:DescribeExecution'], + //TODO: find statemachinename from arn. ARN is part of interface IStateMachine which doesn't have the name property. Confirm with Matt + resources: [`arn:aws:states:*:*:execution:${this.props.stateMachine.stateMachineArn}:${this.props.executionPrefixName || ''}*`], + })); + + this.props.stateMachine.grantStartExecution(options.role); + + // allow the Role access to the Bucket, if there are any inputs/outputs + if ((this.actionProperties.inputs ?? []).length > 0) { + options.bucket.grantRead(options.role); + } + if ((this.actionProperties.outputs || []).length > 0) { + options.bucket.grantWrite(options.role); + } + + return { + configuration: { + StateMachineArn: this.props.stateMachine.stateMachineArn, + Input: this.props.stateMachineInput, + InputType: this.props.stateMachineInputType, + ExecutionNamePrefix: this.props.executionPrefixName, + }, + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index b34c52fc4c7ec..f237b54ec5852 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -89,6 +89,7 @@ "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-stepfunctions": "0.0.0", "case": "1.6.3", "constructs": "^3.0.2" }, @@ -110,6 +111,7 @@ "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-stepfunctions": "0.0.0", "constructs": "^3.0.2" }, "bundledDependencies": [ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts new file mode 100644 index 0000000000000..c5bef23a8179a --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts @@ -0,0 +1,175 @@ +import { expect, haveResourceLike, not } from '@aws-cdk/assert'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as stepfunction from '@aws-cdk/aws-stepfunctions'; +import { Stack } from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import * as cpactions from '../../lib'; +import { StateMachineInputType } from '../../lib'; + +export = { + 'StepFunctions Invoke Action': { + 'Verify stepfunction configuration properties are set to specific values'(test: Test) { + const stack = new Stack(); + + //when + minimalPipeline(stack, undefined); + + //then + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + 'Stages': [ + //Must have a source stage + { + 'Actions': [ + { + ActionTypeId: { + Category: 'Source', + Owner: 'AWS', + Provider: 'S3', + Version: '1', + }, + 'Configuration': { + S3Bucket: { + Ref: 'MyBucketF68F3FF0', + }, + S3ObjectKey: 'some/path/to', + }, + }, + + ], + }, + // Must have stepfunction invoke action configuration + { + 'Actions': [ + { + ActionTypeId: { + Category: 'Invoke', + Owner: 'AWS', + Provider: 'StepFunctions', + Version: '1', + }, + 'Configuration': { + StateMachineArn: { + Ref: 'SimpleStateMachineE8E2CF40', + }, + InputType: 'Literal', + }, + }, + ], + }, + ], + })); + + expect(stack).to(not(haveResourceLike('AWS::Events::Rule'))); + + test.done(); + }, + + 'Allows the pipeline to invoke this stepfunction'(test: Test) { + const stack = new Stack(); + + minimalPipeline(stack, undefined); + + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': ['states:StartExecution','states:DescribeStateMachine'], + 'Resource': { + 'Ref': 'SimpleStateMachineE8E2CF40', + }, + 'Effect': 'Allow', + }, + ], + }, + })); + + expect(stack).to(haveResourceLike('AWS::IAM::Role')); + + test.done(); + }, + + 'does not allow passing empty StateMachineInput when the StateMachineInputType is FilePath'(test: Test) { + const stack = new Stack(); + + const startState = new stepfunction.Pass(stack, 'StartState'); + + const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { + definition: startState, + }); + + test.throws(() => { + new cpactions.StepFunctionsInvokeAction({ + actionName: 'Invoke', + stateMachine: simpleStateMachine, + stateMachineInputType: StateMachineInputType.FILEPATH, + stateMachineInput: '', + }); + }); + + test.done(); + }, + + 'should throw an exception when the StateMachineInputType is FilePath and no input artifact is provided'(test: Test) { + const stack = new Stack(); + + const startState = new stepfunction.Pass(stack, 'StartState'); + + const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { + definition: startState, + }); + + test.throws(() => { + new cpactions.StepFunctionsInvokeAction({ + actionName: 'Invoke', + stateMachine: simpleStateMachine, + stateMachineInputType: StateMachineInputType.FILEPATH, + stateMachineInput: 'assets/input.json', + }); + }); + + test.done(); + }, + }, +}; + +interface MinimalPipelineOptions { + readonly trigger?: cpactions.S3Trigger; + + readonly bucket?: s3.IBucket; + + readonly bucketKey?: string; +} + +function minimalPipeline(stack: Stack, options: MinimalPipelineOptions = {}): codepipeline.IStage { + const sourceOutput = new codepipeline.Artifact(); + const startState = new stepfunction.Pass(stack, 'StartState'); + const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { + definition: startState, + }); + const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline'); + const sourceStage = pipeline.addStage({ + stageName: 'Source', + actions: [ + new cpactions.S3SourceAction({ + actionName: 'Source', + bucket: options.bucket || new s3.Bucket(stack, 'MyBucket'), + bucketKey: options.bucketKey || 'some/path/to', + output: sourceOutput, + trigger: options.trigger, + }), + ], + }); + pipeline.addStage({ + stageName: 'Invoke', + actions: [ + new cpactions.StepFunctionsInvokeAction({ + actionName: 'Invoke', + stateMachine: simpleStateMachine, + stateMachineInputType: StateMachineInputType.LITERAL, + stateMachineInput: '{}', + }), + ], + }); + return sourceStage; +} From 63a18785ccb71d2fdc5e7133fa38575c11de0c0d Mon Sep 17 00:00:00 2001 From: Madhumitran Loganathan Date: Tue, 7 Jul 2020 16:21:46 -0700 Subject: [PATCH 2/7] feat: allow stepfunctions invoke action Fixed lint issues and verified the changes using unit tests --- .../lib/stepfunctions/invoke-action.ts | 59 ++++++++++--------- .../test.stepfunctions-invoke-actions.ts | 44 +++++++------- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts index 25b7b04c1c37a..532175df21bcc 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts @@ -3,20 +3,20 @@ import * as iam from '@aws-cdk/aws-iam'; import * as stepfunction from '@aws-cdk/aws-stepfunctions'; import { Construct} from '@aws-cdk/core'; import { Action } from '../action'; - + /** * Represents the input type for the StateMachine. */ export enum StateMachineInputType { /** - * When specified, the value in the Input field is passed + * When specified, the value in the Input field is passed * directly to the state machine input. */ LITERAL = 'Literal', /** - * The contents of a file in the input artifact specified by the Input field - * is used as the input for the state machine execution. + * The contents of a file in the input artifact specified by the Input field + * is used as the input for the state machine execution. * Input artifact and FilePath is required when InputType is set to FilePath. */ FILEPATH = 'FilePath', @@ -28,26 +28,26 @@ export enum StateMachineInputType { export interface StepFunctionsInvokeActionProps extends codepipeline.CommonAwsActionProps { /** * The optional input Artifact of the Action. - * If InputType is set to FilePath, this artifact is required + * If InputType is set to FilePath, this artifact is required * and is used to source the input for the state machine execution. - * + * * @default the Action will not have any inputs * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-StepFunctions.html#action-reference-StepFunctions-example */ readonly input?: codepipeline.Artifact; - + /** * The optional output Artifact of the Action. * * @default the Action will not have any outputs */ readonly output?: codepipeline.Artifact; - + /** * The state machine to invoke. */ readonly stateMachine: stepfunction.IStateMachine; - + /** * Optional StateMachine InputType * InputType can be Literal or FilePath @@ -55,36 +55,37 @@ export interface StepFunctionsInvokeActionProps extends codepipeline.CommonAwsAc * @default - Literal */ readonly stateMachineInputType?: StateMachineInputType; - + /**y * When InputType is set to Literal (default), this field is optional. * If provided, the Input field is used directly as the input for the state machine execution. * Otherwise, the state machine is invoked with an empty JSON object {}. - * + * * When InputType is set to FilePath, this field is required. * An input artifact is also required when InputType is set to FilePath. - * + * * @default - none */ readonly stateMachineInput?: string | object; - - + /** * Prefix (optinoal) * - * By default, the action execution ID is used as the state machine execution name. - * If a prefix is provided, it is prepended to the action execution ID with a hyphen and + * By default, the action execution ID is used as the state machine execution name. + * If a prefix is provided, it is prepended to the action execution ID with a hyphen and * together used as the state machine execution name. + * + * @default - action execution ID */ readonly executionPrefixName?: string; } - + /** * CodePipeline invoke Action that is provided by an AWS Step Function. */ export class StepFunctionsInvokeAction extends Action { private readonly props: StepFunctionsInvokeActionProps; - + constructor(props: StepFunctionsInvokeActionProps) { super({ ...props, @@ -99,36 +100,36 @@ export class StepFunctionsInvokeAction extends Action { }, }); - //StateMachineInput is required when the StateMachineInputType is set as FilePath - if ( props.stateMachineInputType == StateMachineInputType.FILEPATH && (!props.stateMachineInput)) { + // StateMachineInput is required when the StateMachineInputType is set as FilePath + if ( props.stateMachineInputType === StateMachineInputType.FILEPATH && (!props.stateMachineInput)) { throw new Error('Input type FilePath was specified, but no filepath was provided'); } - //Input Artifact is required when the StateMachineInputType is set as FilePath - if ( props.stateMachineInputType == StateMachineInputType.FILEPATH && (!this.actionProperties.inputs)) { + // Input Artifact is required when the StateMachineInputType is set as FilePath + if ( props.stateMachineInputType === StateMachineInputType.FILEPATH && (!this.actionProperties.inputs)) { throw new Error('Input type FilePath was specified, but no input artifact was provided'); } - + this.props = props; } - + protected bound(_scope: Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { // allow pipeline to invoke this step function options.role.addToPolicy(new iam.PolicyStatement({ - actions: ['states:StartExecution','states:DescribeStateMachine'], + actions: ['states:StartExecution', 'states:DescribeStateMachine'], resources: [this.props.stateMachine.stateMachineArn], })); - + // allow action executions to be inspected and invoked options.role.addToPolicy(new iam.PolicyStatement({ actions: ['states:DescribeExecution'], - //TODO: find statemachinename from arn. ARN is part of interface IStateMachine which doesn't have the name property. Confirm with Matt + // TODO: find statemachinename from arn. ARN is part of interface IStateMachine which doesn't have the name property. Confirm with Matt resources: [`arn:aws:states:*:*:execution:${this.props.stateMachine.stateMachineArn}:${this.props.executionPrefixName || ''}*`], })); - + this.props.stateMachine.grantStartExecution(options.role); - + // allow the Role access to the Bucket, if there are any inputs/outputs if ((this.actionProperties.inputs ?? []).length > 0) { options.bucket.grantRead(options.role); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts index c5bef23a8179a..fe07bbba63a81 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts @@ -5,22 +5,21 @@ import * as stepfunction from '@aws-cdk/aws-stepfunctions'; import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; -import { StateMachineInputType } from '../../lib'; export = { 'StepFunctions Invoke Action': { 'Verify stepfunction configuration properties are set to specific values'(test: Test) { const stack = new Stack(); - //when + // when minimalPipeline(stack, undefined); - //then + // then expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { - 'Stages': [ - //Must have a source stage + Stages: [ + // Must have a source stage { - 'Actions': [ + Actions: [ { ActionTypeId: { Category: 'Source', @@ -28,19 +27,18 @@ export = { Provider: 'S3', Version: '1', }, - 'Configuration': { + Configuration: { S3Bucket: { Ref: 'MyBucketF68F3FF0', }, S3ObjectKey: 'some/path/to', }, }, - ], }, // Must have stepfunction invoke action configuration { - 'Actions': [ + Actions: [ { ActionTypeId: { Category: 'Invoke', @@ -48,7 +46,7 @@ export = { Provider: 'StepFunctions', Version: '1', }, - 'Configuration': { + Configuration: { StateMachineArn: { Ref: 'SimpleStateMachineE8E2CF40', }, @@ -63,22 +61,22 @@ export = { expect(stack).to(not(haveResourceLike('AWS::Events::Rule'))); test.done(); - }, + }, 'Allows the pipeline to invoke this stepfunction'(test: Test) { const stack = new Stack(); minimalPipeline(stack, undefined); - + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': [ + PolicyDocument: { + Statement: [ { - 'Action': ['states:StartExecution','states:DescribeStateMachine'], - 'Resource': { - 'Ref': 'SimpleStateMachineE8E2CF40', + Action: ['states:StartExecution', 'states:DescribeStateMachine'], + Resource: { + Ref: 'SimpleStateMachineE8E2CF40', }, - 'Effect': 'Allow', + Effect: 'Allow', }, ], }, @@ -87,7 +85,7 @@ export = { expect(stack).to(haveResourceLike('AWS::IAM::Role')); test.done(); - }, + }, 'does not allow passing empty StateMachineInput when the StateMachineInputType is FilePath'(test: Test) { const stack = new Stack(); @@ -102,7 +100,7 @@ export = { new cpactions.StepFunctionsInvokeAction({ actionName: 'Invoke', stateMachine: simpleStateMachine, - stateMachineInputType: StateMachineInputType.FILEPATH, + stateMachineInputType: cpactions.StateMachineInputType.FILEPATH, stateMachineInput: '', }); }); @@ -123,7 +121,7 @@ export = { new cpactions.StepFunctionsInvokeAction({ actionName: 'Invoke', stateMachine: simpleStateMachine, - stateMachineInputType: StateMachineInputType.FILEPATH, + stateMachineInputType: cpactions.StateMachineInputType.FILEPATH, stateMachineInput: 'assets/input.json', }); }); @@ -146,7 +144,7 @@ function minimalPipeline(stack: Stack, options: MinimalPipelineOptions = {}): co const startState = new stepfunction.Pass(stack, 'StartState'); const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { definition: startState, - }); + }); const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline'); const sourceStage = pipeline.addStage({ stageName: 'Source', @@ -166,7 +164,7 @@ function minimalPipeline(stack: Stack, options: MinimalPipelineOptions = {}): co new cpactions.StepFunctionsInvokeAction({ actionName: 'Invoke', stateMachine: simpleStateMachine, - stateMachineInputType: StateMachineInputType.LITERAL, + stateMachineInputType: cpactions.StateMachineInputType.LITERAL, stateMachineInput: '{}', }), ], From 4caab4554454748feefb24ae559a64c5eb0cabff Mon Sep 17 00:00:00 2001 From: Madhumitran Loganathan Date: Fri, 10 Jul 2020 15:12:06 -0700 Subject: [PATCH 3/7] feat(codepipeline): add support for a StepFunctions invoke action Added validations for input and output artifacts. Stringified StateMachineInput when the input type is literal Tested the changes using unit tests. Modified StateMachineInputType FilePath test to verify based on exception messages. This specific test was previously verifying only if an exception was thrown. Added Integration tests and verified with yarn integ. README file has also been added which explains how to invoke a step function from a pipeline. Reviewed by: sainsbm --- .../aws-codepipeline-actions/README.md | 55 +- .../lib/stepfunctions/invoke-action.ts | 41 +- ...integ.pipeline-stepfunctions.expected.json | 572 ++++++++++++++++++ .../test/integ.pipeline-stepfunctions.ts | 44 ++ .../test.stepfunctions-invoke-actions.ts | 33 +- 5 files changed, 703 insertions(+), 42 deletions(-) create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md index 327a7fd289809..e790abfd166db 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/README.md +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -399,7 +399,7 @@ const buildAction = new codepipeline_actions.CodeBuildAction({ build: { commands: 'export MY_VAR="some value"', }, - }, + }, }), }), variablesNamespace: 'MyNamespace', // optional - by default, a name will be generated for you @@ -794,3 +794,56 @@ new codepipeline_actions.CodeBuildAction({ See [the AWS documentation](https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html) on how to write a Lambda function invoked from CodePipeline. + +#### AWS Step Functions + +This module contains an Action that allows you to invoke a Step Function in a Pipeline: + +```ts +import * as stepfunction from '@aws-cdk/aws-stepfunctions'; + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const startState = new stepfunction.Pass(stack, 'StartState'); +const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { + definition: startState, + }); +const stepFunctionAction = new codepipeline_actions.StepFunctionsInvokeAction({ + actionName: 'Invoke', + stateMachine: simpleStateMachine, + stateMachineInputType: codepipeline_actions.StateMachineInputType.LITERAL, + stateMachineInput: {IsHelloWorldExample: true}, +}); +pipeline.addStage({ + stageName: 'StepFunctions', + actions: [stepFunctionAction], +}); +``` + +The StateMachineInputType can be a Literal or FilePath. +Input artifact and StateMachineInputField are required when +the StateMachineInputType is set as FilePath. + +```ts +import * as stepfunction from '@aws-cdk/aws-stepfunctions'; + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const inputArtifact = new codepipeline.Artifact(); +const startState = new stepfunction.Pass(stack, 'StartState'); +const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { + definition: startState, + }); +const stepFunctionAction = new codepipeline_actions.StepFunctionsInvokeAction({ + actionName: 'Invoke', + input: inputArtifact, + stateMachine: simpleStateMachine, + stateMachineInputType: codepipeline_actions.StateMachineInputType.FILEPATH, + stateMachineInput: 'assets/input.json', +}); +pipeline.addStage({ + stageName: 'StepFunctions', + actions: [stepFunctionAction], +}); +``` + +See [the AWS documentation](https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-StepFunctions.html) +for information on Action structure reference. \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts index 532175df21bcc..fe2e707c54f67 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts @@ -56,7 +56,7 @@ export interface StepFunctionsInvokeActionProps extends codepipeline.CommonAwsAc */ readonly stateMachineInputType?: StateMachineInputType; - /**y + /** * When InputType is set to Literal (default), this field is optional. * If provided, the Input field is used directly as the input for the state machine execution. * Otherwise, the state machine is invoked with an empty JSON object {}. @@ -69,7 +69,7 @@ export interface StepFunctionsInvokeActionProps extends codepipeline.CommonAwsAc readonly stateMachineInput?: string | object; /** - * Prefix (optinoal) + * Prefix (optional) * * By default, the action execution ID is used as the state machine execution name. * If a prefix is provided, it is prepended to the action execution ID with a hyphen and @@ -81,12 +81,22 @@ export interface StepFunctionsInvokeActionProps extends codepipeline.CommonAwsAc } /** - * CodePipeline invoke Action that is provided by an AWS Step Function. + * StepFunction invoke Action that is provided by an AWS CodePipeline. */ export class StepFunctionsInvokeAction extends Action { private readonly props: StepFunctionsInvokeActionProps; constructor(props: StepFunctionsInvokeActionProps) { + if (props.stateMachineInputType === StateMachineInputType.FILEPATH) { + // StateMachineInput is required when the StateMachineInputType is set as FilePath + if (!props.stateMachineInput) { + throw new Error('File path must be specified in the StateMachineInput field when the InputType is FilePath'); + } + // Input Artifact is required when the StateMachineInputType is set as FilePath + if (props.input ?? []) { + throw new Error('Input Artifact must be provided when the InputType is FilePath'); + } + } super({ ...props, resource: props.stateMachine, @@ -98,18 +108,9 @@ export class StepFunctionsInvokeAction extends Action { minOutputs: 0, maxOutputs: 1, }, + inputs: (props.stateMachineInputType === StateMachineInputType.FILEPATH && props.input) ? [props.input] : [], + outputs: (props.output) ? [props.output] : [], }); - - // StateMachineInput is required when the StateMachineInputType is set as FilePath - if ( props.stateMachineInputType === StateMachineInputType.FILEPATH && (!props.stateMachineInput)) { - throw new Error('Input type FilePath was specified, but no filepath was provided'); - } - - // Input Artifact is required when the StateMachineInputType is set as FilePath - if ( props.stateMachineInputType === StateMachineInputType.FILEPATH && (!this.actionProperties.inputs)) { - throw new Error('Input type FilePath was specified, but no input artifact was provided'); - } - this.props = props; } @@ -121,27 +122,25 @@ export class StepFunctionsInvokeAction extends Action { resources: [this.props.stateMachine.stateMachineArn], })); - // allow action executions to be inspected and invoked + // allow state machine executions to be inspected options.role.addToPolicy(new iam.PolicyStatement({ actions: ['states:DescribeExecution'], - // TODO: find statemachinename from arn. ARN is part of interface IStateMachine which doesn't have the name property. Confirm with Matt - resources: [`arn:aws:states:*:*:execution:${this.props.stateMachine.stateMachineArn}:${this.props.executionPrefixName || ''}*`], + resources: [`arn:aws:states:*:*:execution:${this.props.stateMachine.stateMachineArn}:${this.props.executionPrefixName ?? ''}*`], })); - this.props.stateMachine.grantStartExecution(options.role); - // allow the Role access to the Bucket, if there are any inputs/outputs if ((this.actionProperties.inputs ?? []).length > 0) { options.bucket.grantRead(options.role); } - if ((this.actionProperties.outputs || []).length > 0) { + if ((this.actionProperties.outputs ?? []).length > 0) { options.bucket.grantWrite(options.role); } return { configuration: { StateMachineArn: this.props.stateMachine.stateMachineArn, - Input: this.props.stateMachineInput, + Input: (this.props.stateMachineInputType === StateMachineInputType.LITERAL) ? + JSON.stringify(this.props.stateMachineInput) : this.props.stateMachineInput, InputType: this.props.stateMachineInputType, ExecutionNamePrefix: this.props.executionPrefixName, }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json new file mode 100644 index 0000000000000..2df2e0b61d2a7 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json @@ -0,0 +1,572 @@ +{ + "Resources": { + "SimpleStateMachineRole0CBC135A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "SimpleStateMachineE8E2CF40": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "SimpleStateMachineRole0CBC135A", + "Arn" + ] + }, + "DefinitionString": "{\"StartAt\":\"StartState\",\"States\":{\"StartState\":{\"Type\":\"Pass\",\"End\":true}}}" + }, + "DependsOn": [ + "SimpleStateMachineRole0CBC135A" + ] + }, + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "MyPipelineRoleC0D47CA4", + "Arn" + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "MyPipelineSourceCodePipelineActionRoleAA05D76F", + "Arn" + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MyPipelineArtifactsBucket727923DD": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyPipelineArtifactsBucketEncryptionKeyAlias9D4F8C59": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-awscdkcodepipelinestepfunctionsmypipelinece88aa28", + "TargetKeyId": { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "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": [ + "MyPipelineArtifactsBucket727923DD", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucket727923DD", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyPipelineSourceCodePipelineActionRoleAA05D76F", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyPipelineInvokeCodePipelineActionRole006B5BAD", + "Arn" + ] + } + } + ], + "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/to", + "PollForSourceChanges": true + }, + "Name": "Source", + "OutputArtifacts": [ + { + "Name": "Artifact_Source_Source" + } + ], + "RoleArn": { + "Fn::GetAtt": [ + "MyPipelineSourceCodePipelineActionRoleAA05D76F", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Invoke", + "Owner": "AWS", + "Provider": "StepFunctions", + "Version": "1" + }, + "Configuration": { + "StateMachineArn": { + "Ref": "SimpleStateMachineE8E2CF40" + }, + "Input": "{\"IsHelloWorldExample\":true}", + "InputType": "Literal" + }, + "Name": "Invoke", + "RoleArn": { + "Fn::GetAtt": [ + "MyPipelineInvokeCodePipelineActionRole006B5BAD", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "Invoke" + } + ], + "ArtifactStore": { + "EncryptionKey": { + "Id": { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", + "Arn" + ] + }, + "Type": "KMS" + }, + "Location": { + "Ref": "MyPipelineArtifactsBucket727923DD" + }, + "Type": "S3" + } + }, + "DependsOn": [ + "MyPipelineRoleDefaultPolicy34F09EFA", + "MyPipelineRoleC0D47CA4" + ] + }, + "MyPipelineSourceCodePipelineActionRoleAA05D76F": { + "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" + } + } + }, + "MyPipelineSourceCodePipelineActionRoleDefaultPolicy10C831A9": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucket727923DD", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucket727923DD", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyPipelineSourceCodePipelineActionRoleDefaultPolicy10C831A9", + "Roles": [ + { + "Ref": "MyPipelineSourceCodePipelineActionRoleAA05D76F" + } + ] + } + }, + "MyPipelineInvokeCodePipelineActionRole006B5BAD": { + "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" + } + } + }, + "MyPipelineInvokeCodePipelineActionRoleDefaultPolicy07A602B1": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "states:StartExecution", + "states:DescribeStateMachine" + ], + "Effect": "Allow", + "Resource": { + "Ref": "SimpleStateMachineE8E2CF40" + } + }, + { + "Action": "states:DescribeExecution", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:states:*:*:execution:", + { + "Ref": "SimpleStateMachineE8E2CF40" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyPipelineInvokeCodePipelineActionRoleDefaultPolicy07A602B1", + "Roles": [ + { + "Ref": "MyPipelineInvokeCodePipelineActionRole006B5BAD" + } + ] + } + }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + } + } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts new file mode 100644 index 0000000000000..e8d9fe40cc70e --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts @@ -0,0 +1,44 @@ +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as stepfunctions from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import * as cpactions from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-stepfunctions'); + +const sourceOutput = new codepipeline.Artifact(); + +const startState = new stepfunctions.Pass(stack, 'StartState'); +const simpleStateMachine = new stepfunctions.StateMachine(stack, 'SimpleStateMachine', { + definition: startState, +}); + +const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline'); +pipeline.addStage({ + stageName: 'Source', + actions: [ + new cpactions.S3SourceAction({ + actionName: 'Source', + bucket: new s3.Bucket(stack, 'MyBucket'), + bucketKey: 'some/path/to', + output: sourceOutput, + trigger: cpactions.S3Trigger.POLL, + }), + ], +}); +pipeline.addStage({ + stageName: 'Invoke', + actions: [ + new cpactions.StepFunctionsInvokeAction({ + actionName: 'Invoke', + input: sourceOutput, + stateMachine: simpleStateMachine, + stateMachineInputType: cpactions.StateMachineInputType.LITERAL, + stateMachineInput: {IsHelloWorldExample: true}, + }), + ], +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts index fe07bbba63a81..3ccdc0a600ea9 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts @@ -1,4 +1,4 @@ -import { expect, haveResourceLike, not } from '@aws-cdk/assert'; +import { expect, haveResourceLike } from '@aws-cdk/assert'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as s3 from '@aws-cdk/aws-s3'; import * as stepfunction from '@aws-cdk/aws-stepfunctions'; @@ -12,7 +12,7 @@ export = { const stack = new Stack(); // when - minimalPipeline(stack, undefined); + minimalPipeline(stack); // then expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { @@ -51,6 +51,8 @@ export = { Ref: 'SimpleStateMachineE8E2CF40', }, InputType: 'Literal', + // JSON Stringified input when the input type is Literal + Input: '{\"IsHelloWorldExample\":true}', }, }, ], @@ -58,15 +60,13 @@ export = { ], })); - expect(stack).to(not(haveResourceLike('AWS::Events::Rule'))); - test.done(); }, 'Allows the pipeline to invoke this stepfunction'(test: Test) { const stack = new Stack(); - minimalPipeline(stack, undefined); + minimalPipeline(stack); expect(stack).to(haveResourceLike('AWS::IAM::Policy', { PolicyDocument: { @@ -103,7 +103,7 @@ export = { stateMachineInputType: cpactions.StateMachineInputType.FILEPATH, stateMachineInput: '', }); - }); + }, /File path must be specified in the StateMachineInput field when the InputType is FilePath/); test.done(); }, @@ -124,22 +124,14 @@ export = { stateMachineInputType: cpactions.StateMachineInputType.FILEPATH, stateMachineInput: 'assets/input.json', }); - }); + }, /Input Artifact must be provided when the InputType is FilePath/); test.done(); }, }, }; -interface MinimalPipelineOptions { - readonly trigger?: cpactions.S3Trigger; - - readonly bucket?: s3.IBucket; - - readonly bucketKey?: string; -} - -function minimalPipeline(stack: Stack, options: MinimalPipelineOptions = {}): codepipeline.IStage { +function minimalPipeline(stack: Stack): codepipeline.IStage { const sourceOutput = new codepipeline.Artifact(); const startState = new stepfunction.Pass(stack, 'StartState'); const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { @@ -151,10 +143,10 @@ function minimalPipeline(stack: Stack, options: MinimalPipelineOptions = {}): co actions: [ new cpactions.S3SourceAction({ actionName: 'Source', - bucket: options.bucket || new s3.Bucket(stack, 'MyBucket'), - bucketKey: options.bucketKey || 'some/path/to', + bucket: new s3.Bucket(stack, 'MyBucket'), + bucketKey: 'some/path/to', output: sourceOutput, - trigger: options.trigger, + trigger: cpactions.S3Trigger.POLL, }), ], }); @@ -163,9 +155,10 @@ function minimalPipeline(stack: Stack, options: MinimalPipelineOptions = {}): co actions: [ new cpactions.StepFunctionsInvokeAction({ actionName: 'Invoke', + input: sourceOutput, stateMachine: simpleStateMachine, stateMachineInputType: cpactions.StateMachineInputType.LITERAL, - stateMachineInput: '{}', + stateMachineInput: {IsHelloWorldExample: true}, }), ], }); From a620766fc8e523fb1bb9448cb0f29689654d948d Mon Sep 17 00:00:00 2001 From: Madhumitran Loganathan Date: Thu, 16 Jul 2020 14:59:20 -0700 Subject: [PATCH 4/7] feat(codepipeline): add support for a StepFunctions invoke action Updated the design to include inputArtifact, inputType and input as a single StateMachineInput unit. Modified unit and integration tests based on the design change. Updated README as well. --- .../aws-codepipeline-actions/README.md | 13 +- .../lib/stepfunctions/invoke-action.ts | 104 +- .../aws-codepipeline-actions/package.json | 4 +- ...integ.pipeline-stepfunctions.expected.json | 990 +++++++++--------- .../test/integ.pipeline-stepfunctions.ts | 4 +- .../test.stepfunctions-invoke-actions.ts | 46 +- 6 files changed, 559 insertions(+), 602 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md index e790abfd166db..48aab78cbc539 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/README.md +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -795,7 +795,7 @@ new codepipeline_actions.CodeBuildAction({ See [the AWS documentation](https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html) on how to write a Lambda function invoked from CodePipeline. -#### AWS Step Functions +### AWS Step Functions This module contains an Action that allows you to invoke a Step Function in a Pipeline: @@ -806,12 +806,11 @@ const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); const startState = new stepfunction.Pass(stack, 'StartState'); const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { definition: startState, - }); +}); const stepFunctionAction = new codepipeline_actions.StepFunctionsInvokeAction({ actionName: 'Invoke', stateMachine: simpleStateMachine, - stateMachineInputType: codepipeline_actions.StateMachineInputType.LITERAL, - stateMachineInput: {IsHelloWorldExample: true}, + stateMachineInput: codepipeline_actions.StateMachineInput.literal({IsHelloWorldExample: true}), }); pipeline.addStage({ stageName: 'StepFunctions', @@ -831,13 +830,11 @@ const inputArtifact = new codepipeline.Artifact(); const startState = new stepfunction.Pass(stack, 'StartState'); const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { definition: startState, - }); +}); const stepFunctionAction = new codepipeline_actions.StepFunctionsInvokeAction({ actionName: 'Invoke', - input: inputArtifact, stateMachine: simpleStateMachine, - stateMachineInputType: codepipeline_actions.StateMachineInputType.FILEPATH, - stateMachineInput: 'assets/input.json', + stateMachineInput: codepipeline_actions.StateMachineInput.filePath('assets/input.json', inputArtifact), }); pipeline.addStage({ stageName: 'StepFunctions', diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts index fe2e707c54f67..e39e377d257f8 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts @@ -1,41 +1,70 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as iam from '@aws-cdk/aws-iam'; import * as stepfunction from '@aws-cdk/aws-stepfunctions'; -import { Construct} from '@aws-cdk/core'; +import { Construct } from '@aws-cdk/core'; import { Action } from '../action'; /** - * Represents the input type for the StateMachine. + * Represents the input for the StateMachine. */ -export enum StateMachineInputType { +export class StateMachineInput { /** - * When specified, the value in the Input field is passed - * directly to the state machine input. + * When the input type is FilePath, input artifact and + * filepath must be specified. */ - LITERAL = 'Literal', + public static filePath(filePath: string, inputArtifact: codepipeline.Artifact): StateMachineInput { + return new StateMachineInput(filePath, inputArtifact, 'FilePath'); + } /** - * The contents of a file in the input artifact specified by the Input field - * is used as the input for the state machine execution. - * Input artifact and FilePath is required when InputType is set to FilePath. + * When the input type is Literal, input value is passed + * directly to the state machine input. */ - FILEPATH = 'FilePath', -} + public static literal(object: object): StateMachineInput { + return new StateMachineInput(JSON.stringify(object), undefined, 'Literal'); + } -/** - * Construction properties of the {@link StepFunctionsInvokeAction StepFunction Invoke Action}. - */ -export interface StepFunctionsInvokeActionProps extends codepipeline.CommonAwsActionProps { /** * The optional input Artifact of the Action. * If InputType is set to FilePath, this artifact is required * and is used to source the input for the state machine execution. * - * @default the Action will not have any inputs + * @default - the Action will not have any inputs * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-StepFunctions.html#action-reference-StepFunctions-example */ - readonly input?: codepipeline.Artifact; + public readonly inputArtifact?: codepipeline.Artifact; + + /** + * Optional StateMachine InputType + * InputType can be Literal or FilePath + * + * @default - Literal + */ + public readonly inputType?: string; + + /** + * When InputType is set to Literal (default), the Input field is used + * directly as the input for the state machine execution. + * Otherwise, the state machine is invoked with an empty JSON object {}. + * + * When InputType is set to FilePath, this field is required. + * An input artifact is also required when InputType is set to FilePath. + * + * @default - none + */ + public readonly input: any; + + private constructor(input: any, inputArtifact: codepipeline.Artifact | undefined, inputType: string) { + this.input = input; + this.inputArtifact = inputArtifact; + this.inputType = inputType; + } +} +/** + * Construction properties of the {@link StepFunctionsInvokeAction StepFunction Invoke Action}. + */ +export interface StepFunctionsInvokeActionProps extends codepipeline.CommonAwsActionProps { /** * The optional output Artifact of the Action. * @@ -49,24 +78,12 @@ export interface StepFunctionsInvokeActionProps extends codepipeline.CommonAwsAc readonly stateMachine: stepfunction.IStateMachine; /** - * Optional StateMachine InputType - * InputType can be Literal or FilePath - * - * @default - Literal - */ - readonly stateMachineInputType?: StateMachineInputType; - - /** - * When InputType is set to Literal (default), this field is optional. - * If provided, the Input field is used directly as the input for the state machine execution. - * Otherwise, the state machine is invoked with an empty JSON object {}. - * - * When InputType is set to FilePath, this field is required. - * An input artifact is also required when InputType is set to FilePath. + * Represents the input to the StateMachine. + * This includes input artifact, input type and the statemachine input. * * @default - none */ - readonly stateMachineInput?: string | object; + readonly stateMachineInput?: StateMachineInput; /** * Prefix (optional) @@ -77,7 +94,7 @@ export interface StepFunctionsInvokeActionProps extends codepipeline.CommonAwsAc * * @default - action execution ID */ - readonly executionPrefixName?: string; + readonly executionNamePrefix?: string; } /** @@ -87,16 +104,6 @@ export class StepFunctionsInvokeAction extends Action { private readonly props: StepFunctionsInvokeActionProps; constructor(props: StepFunctionsInvokeActionProps) { - if (props.stateMachineInputType === StateMachineInputType.FILEPATH) { - // StateMachineInput is required when the StateMachineInputType is set as FilePath - if (!props.stateMachineInput) { - throw new Error('File path must be specified in the StateMachineInput field when the InputType is FilePath'); - } - // Input Artifact is required when the StateMachineInputType is set as FilePath - if (props.input ?? []) { - throw new Error('Input Artifact must be provided when the InputType is FilePath'); - } - } super({ ...props, resource: props.stateMachine, @@ -108,7 +115,7 @@ export class StepFunctionsInvokeAction extends Action { minOutputs: 0, maxOutputs: 1, }, - inputs: (props.stateMachineInputType === StateMachineInputType.FILEPATH && props.input) ? [props.input] : [], + inputs: (props.stateMachineInput && props.stateMachineInput.inputArtifact) ? [props.stateMachineInput.inputArtifact] : [], outputs: (props.output) ? [props.output] : [], }); this.props = props; @@ -125,7 +132,7 @@ export class StepFunctionsInvokeAction extends Action { // allow state machine executions to be inspected options.role.addToPolicy(new iam.PolicyStatement({ actions: ['states:DescribeExecution'], - resources: [`arn:aws:states:*:*:execution:${this.props.stateMachine.stateMachineArn}:${this.props.executionPrefixName ?? ''}*`], + resources: [`arn:aws:states:*:*:execution:${this.props.stateMachine.stateMachineArn}:${this.props.executionNamePrefix ?? ''}*`], })); // allow the Role access to the Bucket, if there are any inputs/outputs @@ -139,10 +146,9 @@ export class StepFunctionsInvokeAction extends Action { return { configuration: { StateMachineArn: this.props.stateMachine.stateMachineArn, - Input: (this.props.stateMachineInputType === StateMachineInputType.LITERAL) ? - JSON.stringify(this.props.stateMachineInput) : this.props.stateMachineInput, - InputType: this.props.stateMachineInputType, - ExecutionNamePrefix: this.props.executionPrefixName, + Input: (this.props.stateMachineInput) ? this.props.stateMachineInput.input : [], + InputType: (this.props.stateMachineInput) ? this.props.stateMachineInput.inputType : undefined, + ExecutionNamePrefix: this.props.executionNamePrefix, }, }; } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index f237b54ec5852..158acb5f2e44d 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -88,8 +88,8 @@ "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sns-subscriptions": "0.0.0", - "@aws-cdk/core": "0.0.0", "@aws-cdk/aws-stepfunctions": "0.0.0", + "@aws-cdk/core": "0.0.0", "case": "1.6.3", "constructs": "^3.0.2" }, @@ -110,8 +110,8 @@ "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sns-subscriptions": "0.0.0", - "@aws-cdk/core": "0.0.0", "@aws-cdk/aws-stepfunctions": "0.0.0", + "@aws-cdk/core": "0.0.0", "constructs": "^3.0.2" }, "bundledDependencies": [ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json index 2df2e0b61d2a7..429dbf79d75e5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json @@ -1,572 +1,572 @@ { - "Resources": { - "SimpleStateMachineRole0CBC135A": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "states.", - { - "Ref": "AWS::Region" - }, - ".amazonaws.com" - ] + "Resources": { + "SimpleStateMachineRole0CBC135A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" ] - } + ] } } - ], - "Version": "2012-10-17" - } + } + ], + "Version": "2012-10-17" } - }, - "SimpleStateMachineE8E2CF40": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "RoleArn": { - "Fn::GetAtt": [ - "SimpleStateMachineRole0CBC135A", - "Arn" - ] - }, - "DefinitionString": "{\"StartAt\":\"StartState\",\"States\":{\"StartState\":{\"Type\":\"Pass\",\"End\":true}}}" - }, - "DependsOn": [ - "SimpleStateMachineRole0CBC135A" - ] - }, - "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3": { - "Type": "AWS::KMS::Key", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::", - { - "Ref": "AWS::AccountId" - }, - ":root" - ] - ] - } - }, - "Resource": "*" - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::GetAtt": [ - "MyPipelineRoleC0D47CA4", - "Arn" - ] - } - }, - "Resource": "*" - }, - { - "Action": [ - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::GetAtt": [ - "MyPipelineSourceCodePipelineActionRoleAA05D76F", - "Arn" - ] - } - }, - "Resource": "*" - } - ], - "Version": "2012-10-17" - } + } + }, + "SimpleStateMachineE8E2CF40": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "SimpleStateMachineRole0CBC135A", + "Arn" + ] }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" + "DefinitionString": "{\"StartAt\":\"StartState\",\"States\":{\"StartState\":{\"Type\":\"Pass\",\"End\":true}}}" }, - "MyPipelineArtifactsBucket727923DD": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", - "Arn" + "DependsOn": [ + "SimpleStateMachineRole0CBC135A" + ] + }, + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" ] - }, - "SSEAlgorithm": "aws:kms" - } - } - ] - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true - } - }, - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" - }, - "MyPipelineArtifactsBucketEncryptionKeyAlias9D4F8C59": { - "Type": "AWS::KMS::Alias", - "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinestepfunctionsmypipelinece88aa28", - "TargetKeyId": { - "Fn::GetAtt": [ - "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", - "Arn" - ] - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "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": [ - "MyPipelineArtifactsBucket727923DD", - "Arn" - ] - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "MyPipelineArtifactsBucket727923DD", - "Arn" - ] - }, - "/*" - ] - ] - } - ] }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Effect": "Allow", - "Resource": { + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { "Fn::GetAtt": [ - "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", + "MyPipelineRoleC0D47CA4", "Arn" ] } }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { + "Resource": "*" + }, + { + "Action": [ + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { "Fn::GetAtt": [ "MyPipelineSourceCodePipelineActionRoleAA05D76F", "Arn" ] } }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MyPipelineArtifactsBucket727923DD": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { "Fn::GetAtt": [ - "MyPipelineInvokeCodePipelineActionRole006B5BAD", + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", "Arn" ] - } + }, + "SSEAlgorithm": "aws:kms" } - ], - "Version": "2012-10-17" - }, - "PolicyName": "MyPipelineRoleDefaultPolicy34F09EFA", - "Roles": [ - { - "Ref": "MyPipelineRoleC0D47CA4" } ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true } }, - "MyPipelineAED38ECF": { - "Type": "AWS::CodePipeline::Pipeline", - "Properties": { - "RoleArn": { - "Fn::GetAtt": [ - "MyPipelineRoleC0D47CA4", - "Arn" - ] - }, - "Stages": [ + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyPipelineArtifactsBucketEncryptionKeyAlias9D4F8C59": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-awscdkcodepipelinestepfunctionsmypipelinece88aa28", + "TargetKeyId": { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MyPipelineRoleC0D47CA4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ { - "Actions": [ + "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": [ { - "ActionTypeId": { - "Category": "Source", - "Owner": "AWS", - "Provider": "S3", - "Version": "1" - }, - "Configuration": { - "S3Bucket": { - "Ref": "MyBucketF68F3FF0" - }, - "S3ObjectKey": "some/path/to", - "PollForSourceChanges": true - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Artifact_Source_Source" - } - ], - "RoleArn": { - "Fn::GetAtt": [ - "MyPipelineSourceCodePipelineActionRoleAA05D76F", - "Arn" + "Fn::GetAtt": [ + "MyPipelineArtifactsBucket727923DD", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucket727923DD", + "Arn" + ] + }, + "/*" ] - }, - "RunOrder": 1 + ] } - ], - "Name": "Source" + ] }, { - "Actions": [ - { - "ActionTypeId": { - "Category": "Invoke", - "Owner": "AWS", - "Provider": "StepFunctions", - "Version": "1" - }, - "Configuration": { - "StateMachineArn": { - "Ref": "SimpleStateMachineE8E2CF40" - }, - "Input": "{\"IsHelloWorldExample\":true}", - "InputType": "Literal" - }, - "Name": "Invoke", - "RoleArn": { - "Fn::GetAtt": [ - "MyPipelineInvokeCodePipelineActionRole006B5BAD", - "Arn" - ] - }, - "RunOrder": 1 - } + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" ], - "Name": "Invoke" - } - ], - "ArtifactStore": { - "EncryptionKey": { - "Id": { + "Effect": "Allow", + "Resource": { "Fn::GetAtt": [ "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", "Arn" ] - }, - "Type": "KMS" + } }, - "Location": { - "Ref": "MyPipelineArtifactsBucket727923DD" + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyPipelineSourceCodePipelineActionRoleAA05D76F", + "Arn" + ] + } }, - "Type": "S3" - } - }, - "DependsOn": [ - "MyPipelineRoleDefaultPolicy34F09EFA", - "MyPipelineRoleC0D47CA4" - ] - }, - "MyPipelineSourceCodePipelineActionRoleAA05D76F": { - "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" - ] - ] - } - } + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyPipelineInvokeCodePipelineActionRole006B5BAD", + "Arn" + ] } - ], - "Version": "2012-10-17" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyPipelineRoleDefaultPolicy34F09EFA", + "Roles": [ + { + "Ref": "MyPipelineRoleC0D47CA4" } - } - }, - "MyPipelineSourceCodePipelineActionRoleDefaultPolicy10C831A9": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ + ] + } + }, + "MyPipelineAED38ECF": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "MyPipelineRoleC0D47CA4", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "MyBucketF68F3FF0", - "Arn" - ] + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1" + }, + "Configuration": { + "S3Bucket": { + "Ref": "MyBucketF68F3FF0" }, + "S3ObjectKey": "some/path/to", + "PollForSourceChanges": true + }, + "Name": "Source", + "OutputArtifacts": [ { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "MyBucketF68F3FF0", - "Arn" - ] - }, - "/*" - ] - ] + "Name": "Artifact_Source_Source" } - ] - }, - { - "Action": [ - "s3:DeleteObject*", - "s3:PutObject*", - "s3:Abort*" ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "MyPipelineArtifactsBucket727923DD", - "Arn" - ] - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "MyPipelineArtifactsBucket727923DD", - "Arn" - ] - }, - "/*" - ] - ] - } - ] - }, + "RoleArn": { + "Fn::GetAtt": [ + "MyPipelineSourceCodePipelineActionRoleAA05D76F", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ { - "Action": [ - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Effect": "Allow", - "Resource": { + "ActionTypeId": { + "Category": "Invoke", + "Owner": "AWS", + "Provider": "StepFunctions", + "Version": "1" + }, + "Configuration": { + "StateMachineArn": { + "Ref": "SimpleStateMachineE8E2CF40" + }, + "Input": "{\"IsHelloWorldExample\":true}", + "InputType": "Literal" + }, + "Name": "Invoke", + "RoleArn": { "Fn::GetAtt": [ - "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", + "MyPipelineInvokeCodePipelineActionRole006B5BAD", "Arn" ] - } + }, + "RunOrder": 1 } ], - "Version": "2012-10-17" + "Name": "Invoke" + } + ], + "ArtifactStore": { + "EncryptionKey": { + "Id": { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", + "Arn" + ] + }, + "Type": "KMS" }, - "PolicyName": "MyPipelineSourceCodePipelineActionRoleDefaultPolicy10C831A9", - "Roles": [ - { - "Ref": "MyPipelineSourceCodePipelineActionRoleAA05D76F" - } - ] + "Location": { + "Ref": "MyPipelineArtifactsBucket727923DD" + }, + "Type": "S3" } }, - "MyPipelineInvokeCodePipelineActionRole006B5BAD": { - "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" - ] + "DependsOn": [ + "MyPipelineRoleDefaultPolicy34F09EFA", + "MyPipelineRoleC0D47CA4" + ] + }, + "MyPipelineSourceCodePipelineActionRoleAA05D76F": { + "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" - } + } + ], + "Version": "2012-10-17" } - }, - "MyPipelineInvokeCodePipelineActionRoleDefaultPolicy07A602B1": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "states:StartExecution", - "states:DescribeStateMachine" - ], - "Effect": "Allow", - "Resource": { - "Ref": "SimpleStateMachineE8E2CF40" + } + }, + "MyPipelineSourceCodePipelineActionRoleDefaultPolicy10C831A9": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] } - }, - { - "Action": "states:DescribeExecution", - "Effect": "Allow", - "Resource": { + ] + }, + { + "Action": [ + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucket727923DD", + "Arn" + ] + }, + { "Fn::Join": [ "", [ - "arn:aws:states:*:*:execution:", { - "Ref": "SimpleStateMachineE8E2CF40" + "Fn::GetAtt": [ + "MyPipelineArtifactsBucket727923DD", + "Arn" + ] }, - ":*" + "/*" ] ] } + ] + }, + { + "Action": [ + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", + "Arn" + ] } - ], - "Version": "2012-10-17" - }, - "PolicyName": "MyPipelineInvokeCodePipelineActionRoleDefaultPolicy07A602B1", - "Roles": [ + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyPipelineSourceCodePipelineActionRoleDefaultPolicy10C831A9", + "Roles": [ + { + "Ref": "MyPipelineSourceCodePipelineActionRoleAA05D76F" + } + ] + } + }, + "MyPipelineInvokeCodePipelineActionRole006B5BAD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ { - "Ref": "MyPipelineInvokeCodePipelineActionRole006B5BAD" + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } } - ] + ], + "Version": "2012-10-17" } - }, - "MyBucketF68F3FF0": { - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" } + }, + "MyPipelineInvokeCodePipelineActionRoleDefaultPolicy07A602B1": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "states:StartExecution", + "states:DescribeStateMachine" + ], + "Effect": "Allow", + "Resource": { + "Ref": "SimpleStateMachineE8E2CF40" + } + }, + { + "Action": "states:DescribeExecution", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:states:*:*:execution:", + { + "Ref": "SimpleStateMachineE8E2CF40" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyPipelineInvokeCodePipelineActionRoleDefaultPolicy07A602B1", + "Roles": [ + { + "Ref": "MyPipelineInvokeCodePipelineActionRole006B5BAD" + } + ] + } + }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts index e8d9fe40cc70e..57d6966499dbf 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts @@ -33,10 +33,8 @@ pipeline.addStage({ actions: [ new cpactions.StepFunctionsInvokeAction({ actionName: 'Invoke', - input: sourceOutput, stateMachine: simpleStateMachine, - stateMachineInputType: cpactions.StateMachineInputType.LITERAL, - stateMachineInput: {IsHelloWorldExample: true}, + stateMachineInput: cpactions.StateMachineInput.literal({IsHelloWorldExample: true}), }), ], }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts index 3ccdc0a600ea9..5520c7ac31daf 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts @@ -86,48 +86,6 @@ export = { test.done(); }, - - 'does not allow passing empty StateMachineInput when the StateMachineInputType is FilePath'(test: Test) { - const stack = new Stack(); - - const startState = new stepfunction.Pass(stack, 'StartState'); - - const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { - definition: startState, - }); - - test.throws(() => { - new cpactions.StepFunctionsInvokeAction({ - actionName: 'Invoke', - stateMachine: simpleStateMachine, - stateMachineInputType: cpactions.StateMachineInputType.FILEPATH, - stateMachineInput: '', - }); - }, /File path must be specified in the StateMachineInput field when the InputType is FilePath/); - - test.done(); - }, - - 'should throw an exception when the StateMachineInputType is FilePath and no input artifact is provided'(test: Test) { - const stack = new Stack(); - - const startState = new stepfunction.Pass(stack, 'StartState'); - - const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { - definition: startState, - }); - - test.throws(() => { - new cpactions.StepFunctionsInvokeAction({ - actionName: 'Invoke', - stateMachine: simpleStateMachine, - stateMachineInputType: cpactions.StateMachineInputType.FILEPATH, - stateMachineInput: 'assets/input.json', - }); - }, /Input Artifact must be provided when the InputType is FilePath/); - - test.done(); - }, }, }; @@ -155,10 +113,8 @@ function minimalPipeline(stack: Stack): codepipeline.IStage { actions: [ new cpactions.StepFunctionsInvokeAction({ actionName: 'Invoke', - input: sourceOutput, stateMachine: simpleStateMachine, - stateMachineInputType: cpactions.StateMachineInputType.LITERAL, - stateMachineInput: {IsHelloWorldExample: true}, + stateMachineInput: cpactions.StateMachineInput.literal({IsHelloWorldExample: true}), }), ], }); From 222ba514f498def77c1abe88bfff47c304196ab1 Mon Sep 17 00:00:00 2001 From: Madhumitran Loganathan Date: Fri, 17 Jul 2020 10:54:55 -0700 Subject: [PATCH 5/7] feat(codepipeline): add support for a StepFunctions invoke action Updated the design to include ArtifactPath class Modified unit, integration tests and README accordingly --- .../aws-codepipeline-actions/README.md | 13 ++++++------ .../lib/stepfunctions/invoke-action.ts | 20 +++++++++++-------- ...integ.pipeline-stepfunctions.expected.json | 15 ++++++++++++-- .../test/integ.pipeline-stepfunctions.ts | 4 ++-- .../test.stepfunctions-invoke-actions.ts | 4 +++- 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md index 48aab78cbc539..7bb4d5dec9c49 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/README.md +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -805,12 +805,12 @@ import * as stepfunction from '@aws-cdk/aws-stepfunctions'; const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); const startState = new stepfunction.Pass(stack, 'StartState'); const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { - definition: startState, + definition: startState, }); const stepFunctionAction = new codepipeline_actions.StepFunctionsInvokeAction({ actionName: 'Invoke', stateMachine: simpleStateMachine, - stateMachineInput: codepipeline_actions.StateMachineInput.literal({IsHelloWorldExample: true}), + stateMachineInput: codepipeline_actions.StateMachineInput.literal({ IsHelloWorldExample: true }), }); pipeline.addStage({ stageName: 'StepFunctions', @@ -818,9 +818,8 @@ pipeline.addStage({ }); ``` -The StateMachineInputType can be a Literal or FilePath. -Input artifact and StateMachineInputField are required when -the StateMachineInputType is set as FilePath. +The `StateMachineInput` can be created with one of 2 static factory methods: +`literal`, which takes an arbitrary map as its only argument, or `filePath`: ```ts import * as stepfunction from '@aws-cdk/aws-stepfunctions'; @@ -829,12 +828,12 @@ const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); const inputArtifact = new codepipeline.Artifact(); const startState = new stepfunction.Pass(stack, 'StartState'); const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { - definition: startState, + definition: startState, }); const stepFunctionAction = new codepipeline_actions.StepFunctionsInvokeAction({ actionName: 'Invoke', stateMachine: simpleStateMachine, - stateMachineInput: codepipeline_actions.StateMachineInput.filePath('assets/input.json', inputArtifact), + stateMachineInput: codepipeline_actions.StateMachineInput.filePath(inputArtifact.atPath('assets/input.json')), }); pipeline.addStage({ stageName: 'StepFunctions', diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts index e39e377d257f8..2561692f4f01f 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts @@ -1,7 +1,7 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as iam from '@aws-cdk/aws-iam'; import * as stepfunction from '@aws-cdk/aws-stepfunctions'; -import { Construct } from '@aws-cdk/core'; +import * as cdk from '@aws-cdk/core'; import { Action } from '../action'; /** @@ -12,8 +12,8 @@ export class StateMachineInput { * When the input type is FilePath, input artifact and * filepath must be specified. */ - public static filePath(filePath: string, inputArtifact: codepipeline.Artifact): StateMachineInput { - return new StateMachineInput(filePath, inputArtifact, 'FilePath'); + public static filePath(inputFile: codepipeline.ArtifactPath): StateMachineInput { + return new StateMachineInput(inputFile.location, inputFile.artifact, 'FilePath'); } /** @@ -98,9 +98,9 @@ export interface StepFunctionsInvokeActionProps extends codepipeline.CommonAwsAc } /** - * StepFunction invoke Action that is provided by an AWS CodePipeline. + * StepFunctionInvokeAction that is provided by an AWS CodePipeline. */ -export class StepFunctionsInvokeAction extends Action { +export class StepFunctionInvokeAction extends Action { private readonly props: StepFunctionsInvokeActionProps; constructor(props: StepFunctionsInvokeActionProps) { @@ -121,7 +121,7 @@ export class StepFunctionsInvokeAction extends Action { this.props = props; } - protected bound(_scope: Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): + protected bound(_scope: cdk.Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { // allow pipeline to invoke this step function options.role.addToPolicy(new iam.PolicyStatement({ @@ -131,8 +131,12 @@ export class StepFunctionsInvokeAction extends Action { // allow state machine executions to be inspected options.role.addToPolicy(new iam.PolicyStatement({ - actions: ['states:DescribeExecution'], - resources: [`arn:aws:states:*:*:execution:${this.props.stateMachine.stateMachineArn}:${this.props.executionNamePrefix ?? ''}*`], + resources: [cdk.Stack.of(this.props.stateMachine).formatArn({ + service: 'states', + resource: 'execution', + resourceName: `${this.props.stateMachine.stateMachineArn}:${this.props.executionNamePrefix ?? ''}*`, + sep: ':', + })], })); // allow the Role access to the Bucket, if there are any inputs/outputs diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json index 429dbf79d75e5..9a3bdab6be5bc 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json @@ -537,13 +537,24 @@ } }, { - "Action": "states:DescribeExecution", "Effect": "Allow", "Resource": { "Fn::Join": [ "", [ - "arn:aws:states:*:*:execution:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":states:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":execution:", { "Ref": "SimpleStateMachineE8E2CF40" }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts index 57d6966499dbf..00c4f84831b66 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts @@ -31,10 +31,10 @@ pipeline.addStage({ pipeline.addStage({ stageName: 'Invoke', actions: [ - new cpactions.StepFunctionsInvokeAction({ + new cpactions.StepFunctionInvokeAction({ actionName: 'Invoke', stateMachine: simpleStateMachine, - stateMachineInput: cpactions.StateMachineInput.literal({IsHelloWorldExample: true}), + stateMachineInput: cpactions.StateMachineInput.literal({ IsHelloWorldExample: true }), }), ], }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts index 5520c7ac31daf..21a05c65fbf43 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts @@ -111,12 +111,14 @@ function minimalPipeline(stack: Stack): codepipeline.IStage { pipeline.addStage({ stageName: 'Invoke', actions: [ - new cpactions.StepFunctionsInvokeAction({ + new cpactions.StepFunctionInvokeAction({ actionName: 'Invoke', stateMachine: simpleStateMachine, stateMachineInput: cpactions.StateMachineInput.literal({IsHelloWorldExample: true}), }), ], }); + + cpactions.StateMachineInput.filePath(sourceOutput.atPath('path/to/my/file.json')); return sourceStage; } From c69a47ed3f96bd76b7c393e16befa45bfe814e77 Mon Sep 17 00:00:00 2001 From: Madhumitran Loganathan Date: Fri, 17 Jul 2020 11:00:57 -0700 Subject: [PATCH 6/7] feat(codepipeline): add support for a StepFunctions invoke action Removed a typo from the unit test --- .../test/stepfunctions/test.stepfunctions-invoke-actions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts index 21a05c65fbf43..38dc6f502e7e1 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts @@ -119,6 +119,5 @@ function minimalPipeline(stack: Stack): codepipeline.IStage { ], }); - cpactions.StateMachineInput.filePath(sourceOutput.atPath('path/to/my/file.json')); return sourceStage; } From e2adaf7168b89971a3fa99ba56089951aa5769fb Mon Sep 17 00:00:00 2001 From: Madhumitran Loganathan Date: Fri, 17 Jul 2020 13:16:02 -0700 Subject: [PATCH 7/7] feat: allow stepfunctions invoke action Updated the ternary operator with typescript safe navigation operator Included the action statement Unit and integration tests pass --- .../lib/stepfunctions/invoke-action.ts | 5 +++-- .../test/integ.pipeline-stepfunctions.expected.json | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts index 2561692f4f01f..8d8ff55a69334 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts @@ -131,6 +131,7 @@ export class StepFunctionInvokeAction extends Action { // allow state machine executions to be inspected options.role.addToPolicy(new iam.PolicyStatement({ + actions: ['states:DescribeExecution'], resources: [cdk.Stack.of(this.props.stateMachine).formatArn({ service: 'states', resource: 'execution', @@ -150,8 +151,8 @@ export class StepFunctionInvokeAction extends Action { return { configuration: { StateMachineArn: this.props.stateMachine.stateMachineArn, - Input: (this.props.stateMachineInput) ? this.props.stateMachineInput.input : [], - InputType: (this.props.stateMachineInput) ? this.props.stateMachineInput.inputType : undefined, + Input: this.props.stateMachineInput?.input, + InputType: this.props.stateMachineInput?.inputType, ExecutionNamePrefix: this.props.executionNamePrefix, }, }; diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json index 9a3bdab6be5bc..ed6aaa8b6df4e 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json @@ -537,6 +537,7 @@ } }, { + "Action": "states:DescribeExecution", "Effect": "Allow", "Resource": { "Fn::Join": [