-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Changes from 4 commits
4360d28
63a1878
4caab45
a620766
222ba51
c69a47e
e2adaf7
c574977
5ba3be4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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,53 @@ 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}), | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
}); | ||||||||||||
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. | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
```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, | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
}); | ||||||||||||
const stepFunctionAction = new codepipeline_actions.StepFunctionsInvokeAction({ | ||||||||||||
actionName: 'Invoke', | ||||||||||||
stateMachine: simpleStateMachine, | ||||||||||||
stateMachineInput: codepipeline_actions.StateMachineInput.filePath('assets/input.json', inputArtifact), | ||||||||||||
}); | ||||||||||||
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. |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,155 @@ | ||||||
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 for the StateMachine. | ||||||
*/ | ||||||
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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apologies @madhumitranrl , but I came up with an even better API :). We have a class in the CodePipeline library exactly for this (file in an artifact) purpose: public static filePath(inuputFile: codepipeline.ArtifactPath): StateMachineInput {
return new StateMachineInput(inuputFile.location, inuputFile.artifact, 'FilePath');
} And the customer uses it like this: StateMachineInput .filePath(buildArtifact.atPath('path/to/my/file.json')); This will be perfect! Now, with this design, what do you think about getting rid of export interface StepFunctionInvokeActionProps {
// these 3 same as now
readonly output?: codepipeline.Artifact;
readonly stateMachine: stepfunction.IStateMachine;
readonly executionNamePrefix?: string;
// only one of inputFile and inputObject can be provided - we check that in the StepFunctionsInvokeAction class
readonly inputFile?: codepipeline.ArtifactPath;
readonly inputObject?: object;
} Let me know which one you like better! |
||||||
return new StateMachineInput(filePath, inputArtifact, '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; | ||||||
} | ||||||
|
||||||
/** | ||||||
* StepFunction invoke Action that is provided by an AWS CodePipeline. | ||||||
*/ | ||||||
export class StepFunctionsInvokeAction extends Action { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think of calling it |
||||||
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: 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: [`arn:aws:states:*:*:execution:${this.props.stateMachine.stateMachineArn}:${this.props.executionNamePrefix ?? ''}*`], | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We actually have a method for that in our standard library. This should be: resources: [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) ? this.props.stateMachineInput.input : [], | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have to fill this out if |
||||||
InputType: (this.props.stateMachineInput) ? this.props.stateMachineInput.inputType : undefined, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||
ExecutionNamePrefix: this.props.executionNamePrefix, | ||||||
}, | ||||||
}; | ||||||
} | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 spaces instead of 4: