Skip to content

Commit

Permalink
feat(aws-lambda): add input and output Artifacts to the CodePipeline …
Browse files Browse the repository at this point in the history
…Action. (#1390)

Fixes #1384
  • Loading branch information
skinny85 authored Dec 21, 2018
1 parent 91ecdda commit fbd7728
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 14 deletions.
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
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.
* 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[] {
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;
}
}
}

0 comments on commit fbd7728

Please sign in to comment.