diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts index c3ac1528f9448..0f6ef1754787d 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts @@ -221,8 +221,8 @@ export abstract class Action extends cdk.Construct { public readonly owner: string; public readonly version: string; - private readonly inputArtifacts = new Array(); - private readonly outputArtifacts = new Array(); + private readonly _actionInputArtifacts = new Array(); + private readonly _actionOutputArtifacts = new Array(); private readonly artifactBounds: ActionArtifactBounds; private readonly stage: IStage; @@ -245,9 +245,9 @@ export abstract class Action extends cdk.Construct { } public validate(): string[] { - return validation.validateArtifactBounds('input', this.inputArtifacts, this.artifactBounds.minInputs, + return validation.validateArtifactBounds('input', this._actionInputArtifacts, this.artifactBounds.minInputs, this.artifactBounds.maxInputs, this.category, this.provider) - .concat(validation.validateArtifactBounds('output', this.outputArtifacts, this.artifactBounds.minOutputs, + .concat(validation.validateArtifactBounds('output', this._actionOutputArtifacts, this.artifactBounds.minOutputs, this.artifactBounds.maxOutputs, this.category, this.provider) ); } @@ -268,21 +268,21 @@ export abstract class Action extends cdk.Construct { } public get _inputArtifacts(): Artifact[] { - return this.inputArtifacts.slice(); + return this._actionInputArtifacts.slice(); } public get _outputArtifacts(): Artifact[] { - return this.outputArtifacts.slice(); + return this._actionOutputArtifacts.slice(); } protected addOutputArtifact(name: string = this.stage._internal._generateOutputArtifactName(this)): Artifact { const artifact = new Artifact(this, name); - this.outputArtifacts.push(artifact); + this._actionOutputArtifacts.push(artifact); return artifact; } protected addInputArtifact(artifact: Artifact = this.stage._internal._findInputArtifact(this)): Action { - this.inputArtifacts.push(artifact); + this._actionInputArtifacts.push(artifact); return this; } } diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts index 83177515392b3..554bfd53204ed 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts @@ -262,15 +262,34 @@ export = { const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - // first stage must contain a Source action so we can't use it to test Lambda + const bucket = new s3.Bucket(stack, 'Bucket'); + const sourceStage = pipeline.addStage('Source'); + const source1 = bucket.addToPipeline(sourceStage, 'SourceAction1', { + bucketKey: 'some/key', + outputArtifactName: 'sourceArtifact1', + }); + const source2 = bucket.addToPipeline(sourceStage, 'SourceAction2', { + bucketKey: 'another/key', + outputArtifactName: 'sourceArtifact2', + }); + const stage = new codepipeline.Stage(stack, 'Stage', { pipeline }); - new lambda.PipelineInvokeAction(stack, 'InvokeAction', { + const lambdaAction = new lambda.PipelineInvokeAction(stack, 'InvokeAction', { stage, lambda: lambdaFun, - userParameters: 'foo-bar/42' + userParameters: 'foo-bar/42', + inputArtifacts: [ + source2.outputArtifact, + source1.outputArtifact, + ], + outputArtifactNames: [ + 'lambdaOutput1', + 'lambdaOutput2', + 'lambdaOutput3', + ], }); - expect(stack, /* skip validation */ true).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "ArtifactStore": { "Location": { "Ref": "PipelineArtifactsBucket22248F97" @@ -284,6 +303,9 @@ export = { ] }, "Stages": [ + { + "Name": "Source", + }, { "Actions": [ { @@ -299,9 +321,16 @@ export = { }, "UserParameters": "foo-bar/42" }, - "InputArtifacts": [], + "InputArtifacts": [ + { "Name": "sourceArtifact2" }, + { "Name": "sourceArtifact1" }, + ], "Name": "InvokeAction", - "OutputArtifacts": [], + "OutputArtifacts": [ + { "Name": "lambdaOutput1" }, + { "Name": "lambdaOutput2" }, + { "Name": "lambdaOutput3" }, + ], "RunOrder": 1 } ], @@ -310,6 +339,9 @@ export = { ] })); + test.equal(lambdaAction.outputArtifacts().length, 3); + test.notEqual(lambdaAction.outputArtifact('lambdaOutput2'), undefined); + expect(stack, /* skip validation */ true).to(haveResource('AWS::IAM::Policy', { "PolicyDocument": { "Statement": [ diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index fb56ce6ceda5c..ac5afe6b50df2 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -87,6 +87,25 @@ You can also add the Lambda to the Pipeline directly: fn.addToPipeline(lambdaStage, 'Lambda'); ``` +The Lambda Action can have up to 5 inputs, +and up to 5 outputs: + +```typescript +const lambdaAction = fn.addToPipeline(lambdaStage, 'Lambda', { + inputArtifacts: [ + sourceAction.outputArtifact, + buildAction.outputArtifact, + ], + outputArtifactNames: [ + 'Out1', + 'Out2', + ], +}); + +lambdaAction.outputArtifacts(); // returns the list of output Artifacts +lambdaAction.outputArtifact('Out2'); // returns the named output Artifact, or throws an exception if not found +``` + 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. diff --git a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts index 1dea4cd5d5ed9..a5a930c355ed4 100644 --- a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts @@ -9,6 +9,33 @@ import { FunctionRef } from './lambda-ref'; * or through {@link FunctionRef#addToPipeline}. */ export interface CommonPipelineInvokeActionProps extends codepipeline.CommonActionProps { + // because of @see links + // tslint:disable:max-line-length + + /** + * The optional input Artifacts of the Action. + * A Lambda Action can have up to 5 inputs. + * The inputs will appear in the event passed to the Lambda, + * under the `'CodePipeline.job'.data.inputArtifacts` path. + * + * @default the Action will not have any inputs + * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-json-event-example + */ + inputArtifacts?: codepipeline.Artifact[]; + + // tslint:enable:max-line-length + + /** + * The optional names of the output Artifacts of the Action. + * A Lambda Action can have up to 5 outputs. + * The outputs will appear in the event passed to the Lambda, + * under the `'CodePipeline.job'.data.outputArtifacts` path. + * It is the responsibility of the Lambda to upload ZIP files with the Artifact contents to the provided locations. + * + * @default the Action will not have any outputs + */ + outputArtifactNames?: string[]; + /** * String to be used in the event data parameter passed to the Lambda * function @@ -67,6 +94,16 @@ export class PipelineInvokeAction extends codepipeline.Action { } }); + // handle input artifacts + for (const inputArtifact of props.inputArtifacts || []) { + this.addInputArtifact(inputArtifact); + } + + // handle output artifacts + for (const outputArtifactName of props.outputArtifactNames || []) { + this.addOutputArtifact(outputArtifactName); + } + // allow pipeline to list functions props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() .addAction('lambda:ListFunctions') @@ -86,4 +123,17 @@ export class PipelineInvokeAction extends codepipeline.Action { .addAction('codepipeline:PutJobFailureResult')); } } + + public outputArtifacts(): codepipeline.Artifact[] { + return this._outputArtifacts; + } + + public outputArtifact(artifactName: string): codepipeline.Artifact { + const result = this._outputArtifacts.find(a => (a.name === artifactName)); + if (result === undefined) { + throw new Error(`Could not find the output Artifact with name '${artifactName}'`); + } else { + return result; + } + } }