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(aws-lambda): add input and output Artifacts to the CodePipeline Action #1390

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions packages/@aws-cdk/aws-codepipeline-api/lib/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ export abstract class Action extends cdk.Construct {
public readonly owner: string;
public readonly version: string;

private readonly inputArtifacts = new Array<Artifact>();
private readonly outputArtifacts = new Array<Artifact>();
private readonly _actionInputArtifacts = new Array<Artifact>();
private readonly _actionOutputArtifacts = new Array<Artifact>();
private readonly artifactBounds: ActionArtifactBounds;
private readonly stage: IStage;

Expand All @@ -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)
);
}
Expand All @@ -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;
}
}
Expand Down
44 changes: 38 additions & 6 deletions packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -284,6 +303,9 @@ export = {
]
},
"Stages": [
{
"Name": "Source",
},
{
"Actions": [
{
Expand All @@ -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
}
],
Expand All @@ -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": [
Expand Down
19 changes: 19 additions & 0 deletions packages/@aws-cdk/aws-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
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.

Expand Down
50 changes: 50 additions & 0 deletions packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe provide some pointers as to how to read the input artifacts from the lambda code and how to produce the output artifacts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

* 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
Expand Down Expand Up @@ -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')
Expand All @@ -86,4 +123,17 @@ export class PipelineInvokeAction extends codepipeline.Action {
.addAction('codepipeline:PutJobFailureResult'));
}
}

public outputArtifacts(): codepipeline.Artifact[] {
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
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;
}
}
}