Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(codepipeline): add support for a StepFunctions invoke action #8931

Merged
merged 9 commits into from
Jul 18, 2020
51 changes: 50 additions & 1 deletion packages/@aws-cdk/aws-codepipeline-actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -794,3 +794,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.
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
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({
resources: [cdk.Stack.of(this.props.stateMachine).formatArn({
service: 'states',
resource: 'execution',
resourceName: `${this.props.stateMachine.stateMachineArn}:${this.props.executionNamePrefix ?? ''}*`,
sep: ':',
})],
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
}));

// 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) ? this.props.stateMachineInput.input : [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have to fill this out if stateMachineInput is undefined? We can't make it undefined as well?

InputType: (this.props.stateMachineInput) ? this.props.stateMachineInput.inputType : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to TypeScript's safe navigation operator, this can be simplified to:

Suggested change
InputType: (this.props.stateMachineInput) ? this.props.stateMachineInput.inputType : undefined,
InputType: this.props.stateMachineInput?.inputType,

ExecutionNamePrefix: this.props.executionNamePrefix,
},
};
}
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-codepipeline-actions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
"@aws-cdk/core": "0.0.0",
"case": "1.6.3",
"constructs": "^3.0.2"
Expand All @@ -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",
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
"@aws-cdk/core": "0.0.0",
"constructs": "^3.0.2"
},
Expand Down
Loading