diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md index 0c4a3908761eb..825f15b8c2662 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/README.md +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -801,3 +801,52 @@ 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, + stateMachineInput: codepipeline_actions.StateMachineInput.literal({ IsHelloWorldExample: true }), +}); +pipeline.addStage({ + stageName: 'StepFunctions', + actions: [stepFunctionAction], +}); +``` + +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'; + +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', + stateMachine: simpleStateMachine, + stateMachineInput: codepipeline_actions.StateMachineInput.filePath(inputArtifact.atPath('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/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..8d8ff55a69334 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts @@ -0,0 +1,160 @@ +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 * as cdk from '@aws-cdk/core'; +import { Action } from '../action'; + +/** + * Represents the input for the StateMachine. + */ +export class StateMachineInput { + /** + * When the input type is FilePath, input artifact and + * filepath must be specified. + */ + public static filePath(inputFile: codepipeline.ArtifactPath): StateMachineInput { + return new StateMachineInput(inputFile.location, inputFile.artifact, 'FilePath'); + } + + /** + * When the input type is Literal, input value is passed + * directly to the state machine input. + */ + public static literal(object: object): StateMachineInput { + return new StateMachineInput(JSON.stringify(object), undefined, 'Literal'); + } + + /** + * 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 + */ + 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. + * + * @default the Action will not have any outputs + */ + readonly output?: codepipeline.Artifact; + + /** + * The state machine to invoke. + */ + readonly stateMachine: stepfunction.IStateMachine; + + /** + * Represents the input to the StateMachine. + * This includes input artifact, input type and the statemachine input. + * + * @default - none + */ + readonly stateMachineInput?: StateMachineInput; + + /** + * 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 + * together used as the state machine execution name. + * + * @default - action execution ID + */ + readonly executionNamePrefix?: string; +} + +/** + * StepFunctionInvokeAction that is provided by an AWS CodePipeline. + */ +export class StepFunctionInvokeAction 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, + }, + inputs: (props.stateMachineInput && props.stateMachineInput.inputArtifact) ? [props.stateMachineInput.inputArtifact] : [], + outputs: (props.output) ? [props.output] : [], + }); + this.props = props; + } + + 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({ + actions: ['states:StartExecution', 'states:DescribeStateMachine'], + resources: [this.props.stateMachine.stateMachineArn], + })); + + // 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', + resourceName: `${this.props.stateMachine.stateMachineArn}:${this.props.executionNamePrefix ?? ''}*`, + sep: ':', + })], + })); + + // 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?.input, + InputType: this.props.stateMachineInput?.inputType, + ExecutionNamePrefix: this.props.executionNamePrefix, + }, + }; + } +} \ 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 4767b49a361b0..de7642c8649db 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -88,6 +88,7 @@ "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sns-subscriptions": "0.0.0", + "@aws-cdk/aws-stepfunctions": "0.0.0", "@aws-cdk/core": "0.0.0", "case": "1.6.3", "constructs": "^3.0.2" @@ -109,6 +110,7 @@ "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sns-subscriptions": "0.0.0", + "@aws-cdk/aws-stepfunctions": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.0.2" }, 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..ed6aaa8b6df4e --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json @@ -0,0 +1,584 @@ +{ + "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:", + { + "Ref": "AWS::Partition" + }, + ":states:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":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..00c4f84831b66 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.ts @@ -0,0 +1,42 @@ +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.StepFunctionInvokeAction({ + actionName: 'Invoke', + stateMachine: simpleStateMachine, + stateMachineInput: cpactions.StateMachineInput.literal({ 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 new file mode 100644 index 0000000000000..38dc6f502e7e1 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/stepfunctions/test.stepfunctions-invoke-actions.ts @@ -0,0 +1,123 @@ +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'; +import { Stack } from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import * as cpactions from '../../lib'; + +export = { + 'StepFunctions Invoke Action': { + 'Verify stepfunction configuration properties are set to specific values'(test: Test) { + const stack = new Stack(); + + // when + minimalPipeline(stack); + + // 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', + // JSON Stringified input when the input type is Literal + Input: '{\"IsHelloWorldExample\":true}', + }, + }, + ], + }, + ], + })); + + test.done(); + }, + + 'Allows the pipeline to invoke this stepfunction'(test: Test) { + const stack = new Stack(); + + minimalPipeline(stack); + + 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(); + }, + }, +}; + +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', { + definition: startState, + }); + const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline'); + const sourceStage = 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.StepFunctionInvokeAction({ + actionName: 'Invoke', + stateMachine: simpleStateMachine, + stateMachineInput: cpactions.StateMachineInput.literal({IsHelloWorldExample: true}), + }), + ], + }); + + return sourceStage; +}