Skip to content

Commit

Permalink
feat(lambda): introduce a new kind of Code, CodePipelineCode.
Browse files Browse the repository at this point in the history
  • Loading branch information
skinny85 committed Mar 15, 2019
1 parent 2d463be commit f825d39
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 6 deletions.
64 changes: 63 additions & 1 deletion packages/@aws-cdk/aws-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ fn.addEventSource(new S3EventSource(bucket, {

See the documentation for the __@aws-cdk/aws-lambda-event-sources__ module for more details.

### Lambda in CodePipeline
### Lambda invoked in CodePipeline

This module also contains an Action that allows you to invoke a Lambda function from CodePipeline:

Expand Down Expand Up @@ -120,6 +120,68 @@ lambdaAction.outputArtifact('Out2'); // returns the named output Artifact, or th
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.

### Lambda deployed through CodePipeline

If you want to deploy your Lambda through CodePipeline,
you can use the special `Code` convenience method, `lambda.Code.codePipeline`.
Note that your Lambda must be in a separate Stack than your Pipeline.
The Lambda itself will be deployed, alongside the entire Stack it belongs to,
using a CloudFormation CodePipeline Action. Example:

```typescript
const lambdaCode = lambda.Code.codePipeline();
const lambda = new lambda.Function(lambdaStack, 'Lambda', {
code: lambdaCode,
handler: 'index.handler',
runtime: lambda.Runtime.NodeJS810,
});

// other resources that your Lambda needs, added to the lambdaStack...

const pipeline = new codepipeline.Pipeline(pipelineStack, 'Pipeline');

// add the source code repository containing this code to your Pipeline,
// and the source code of the Lambda Function, if they're separate
pipeline.addStage({
name: 'Source',
actions: [
// ...
],
});

// add a build Action to your Pipeline,
// that calls `cdk synth` on the lambdaStack,
// and saves it to some file,
// and a separate build for your Lambda source code - if needed
pipeline.addStage({
name: 'Build',
actions: [
lambdaBuildAction,
cdkBuildAction,
],
});

// finally, deploy your Lambda code
const parameterOverrides: { [name: string]: any } = {};
parameterOverrides[lambdaCode.bucketNameParam.logicalId] = lambdaBuildAction.outputArtifact.bucketName;
parameterOverrides[lambdaCode.bucketKeyParam.logicalId] = lambdaBuildAction.outputArtifact.objectKey;
pipeline.addStage({
name: 'Deploy',
actions: [
new cloudformation.PipelineCreateUpdateStackAction({
actionName: 'Lambda_CFN_Deploy',
templatePath: cdkBuildAction.outputArtifact.atPath('template.yaml'),
stackName: 'YourDeployStackHere',
adminPermissions: true,
parameterOverrides,
additionalInputArtifacts: [
lambdaBuildAction,
],
}),
],
});
```

### Lambda with DLQ

```ts
Expand Down
66 changes: 63 additions & 3 deletions packages/@aws-cdk/aws-lambda/lib/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export abstract class Code {
return new AssetCode(filePath, assets.AssetPackaging.File);
}

public static codePipeline(props?: CodePipelineCodeProps): CodePipelineCode {
return new CodePipelineCode(props);
}

/**
* Determines whether this Code is inline code or not.
*/
Expand Down Expand Up @@ -150,8 +154,8 @@ export class AssetCode extends Code {
this.packaging = packaging;
} else {
this.packaging = fs.lstatSync(path).isDirectory()
? assets.AssetPackaging.ZipDirectory
: assets.AssetPackaging.File;
? assets.AssetPackaging.ZipDirectory
: assets.AssetPackaging.File;
}
}

Expand All @@ -175,9 +179,65 @@ export class AssetCode extends Code {
this.asset!.addResourceMetadata(resource, 'Code');
}

return {
return {
s3Bucket: this.asset!.s3BucketName,
s3Key: this.asset!.s3ObjectKey
};
}
}

export interface CodePipelineCodeProps {
bucketNameParam?: cdk.CfnParameter;

bucketKeyParam?: cdk.CfnParameter;
}

export class CodePipelineCode extends Code {
public readonly isInline = false;
private _bucketNameParam?: cdk.CfnParameter;
private _bucketKeyParam?: cdk.CfnParameter;

constructor(props: CodePipelineCodeProps = {}) {
super();

this._bucketNameParam = props.bucketNameParam;
this._bucketKeyParam = props.bucketKeyParam;
}

public bind(construct: cdk.Construct) {
if (!this._bucketNameParam) {
this._bucketNameParam = new cdk.CfnParameter(construct, 'LambdaSourceBucketNameParameter', {
type: 'String',
});
}

if (!this._bucketKeyParam) {
this._bucketKeyParam = new cdk.CfnParameter(construct, 'LambdaSourceBucketKeyParameter', {
type: 'String',
});
}
}

public _toJSON(_?: cdk.CfnResource): CfnFunction.CodeProperty {
return {
s3Bucket: this.bucketNameParam.stringValue,
s3Key: this.bucketKeyParam.stringValue,
};
}

public get bucketNameParam(): cdk.CfnParameter {
if (this._bucketNameParam) {
return this._bucketNameParam;
} else {
throw new Error('You have to use CodePipelineCode to create a Lambda Function before you can access the bucketNameParam property');
}
}

public get bucketKeyParam(): cdk.CfnParameter {
if (this._bucketKeyParam) {
return this._bucketKeyParam;
} else {
throw new Error('You have to use CodePipelineCode to create a Lambda Function before you can access the bucketKeyParam property');
}
}
}
83 changes: 81 additions & 2 deletions packages/@aws-cdk/aws-lambda/test/test.code.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, haveResource, ResourcePart } from '@aws-cdk/assert';
import { expect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert';
import assets = require('@aws-cdk/assets');
import cdk = require('@aws-cdk/cdk');
import cxapi = require('@aws-cdk/cx-api');
Expand Down Expand Up @@ -93,7 +93,86 @@ export = {
}, ResourcePart.CompleteDefinition));
test.done();
}
}
},

'lambda.Code.codepipeline': {
"automatically creates the Bucket and Key parameters when it's used in a Function"(test: Test) {
const stack = new cdk.Stack();
const code = new lambda.CodePipelineCode();
new lambda.Function(stack, 'Function', {
code,
runtime: lambda.Runtime.NodeJS810,
handler: 'index.handler',
});

expect(stack).to(haveResourceLike('AWS::Lambda::Function', {
Code: {
S3Bucket: {
Ref: "FunctionLambdaSourceBucketNameParameter9E9E108F",
},
S3Key: {
Ref: "FunctionLambdaSourceBucketKeyParameter5BBE948B",
},
},
}));

test.notEqual(code.bucketNameParam, undefined);
test.notEqual(code.bucketKeyParam, undefined);

test.done();
},

'does not allow accessing the Parameter properties before being used in a Function'(test: Test) {
const code = new lambda.CodePipelineCode();

test.throws(() => {
test.notEqual(code.bucketNameParam, undefined);
}, /Function/);

test.throws(() => {
test.notEqual(code.bucketKeyParam, undefined);
}, /Function/);

test.done();
},

'allows passing custom Parameters when creating it'(test: Test) {
const stack = new cdk.Stack();
const bucketNameParam = new cdk.CfnParameter(stack, 'BucketNameParam', {
type: 'String',
});
const bucketKeyParam = new cdk.CfnParameter(stack, 'BucketKeyParam', {
type: 'String',
});

const code = lambda.Code.codePipeline({
bucketNameParam,
bucketKeyParam,
});

new lambda.Function(stack, 'Function', {
code,
runtime: lambda.Runtime.NodeJS810,
handler: 'index.handler',
});

expect(stack).to(haveResourceLike('AWS::Lambda::Function', {
Code: {
S3Bucket: {
Ref: "BucketNameParam",
},
S3Key: {
Ref: "BucketKeyParam",
},
},
}));

test.notEqual(code.bucketNameParam, undefined);
test.notEqual(code.bucketKeyParam, undefined);

test.done();
},
},
};

function defineFunction(code: lambda.Code, runtime: lambda.Runtime = lambda.Runtime.NodeJS810) {
Expand Down

0 comments on commit f825d39

Please sign in to comment.