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(core,s3-assets,lambda): custom asset bundling #7898

Merged
merged 64 commits into from
Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
3840a4a
feat(lambda): docker code
jogold May 10, 2020
4afe2f5
Merge branch 'master' into lambda-docker-code
jogold May 16, 2020
ca62f37
environment
jogold May 16, 2020
116d985
fix integ test
jogold May 16, 2020
d2a66c8
.gitignore in python-lambda-handler
jogold May 16, 2020
7120eba
fix integ test 2
jogold May 16, 2020
13057c8
start of README
jogold May 16, 2020
aad39af
calcSourceHash
jogold May 16, 2020
2d780e6
AssetOptions
jogold May 16, 2020
3d3e7c2
PR feedback
jogold May 18, 2020
4f68287
docker -> bundling
jogold May 18, 2020
8999ded
default to lambdaci/lambda
jogold May 18, 2020
d98bc3c
/src and /bundle
jogold May 18, 2020
3a10273
/src is the workdir
jogold May 18, 2020
97c81d4
README
jogold May 18, 2020
ea27118
Merge branch 'master' into lambda-docker-code
jogold May 20, 2020
f5011d3
move to s3-assets
jogold May 20, 2020
203e166
jsdoc
jogold May 20, 2020
21c10a4
README
jogold May 20, 2020
498f560
fix integ test
jogold May 20, 2020
c37ed4a
reverts
jogold May 20, 2020
e91147e
experimental and internal
jogold May 20, 2020
248e7f8
flip order
jogold May 20, 2020
7ada5b7
s3-assets integ, README, JSDoc
jogold May 21, 2020
4545e8b
README
jogold May 21, 2020
599743d
Merge branch 'master' into lambda-docker-code
jogold May 21, 2020
902e600
JSDoc
jogold May 21, 2020
7bdf068
/asset-input and /asset-output
jogold May 21, 2020
8d999e4
README
jogold May 22, 2020
c142de0
bundleAssetPath in cwd
jogold May 22, 2020
4685d7b
try catch around _run
jogold May 22, 2020
453f188
fix bundling test
jogold May 22, 2020
bfb085f
Merge branch 'master' into lambda-docker-code
jogold May 26, 2020
c1422d6
add link to docker run doc
jogold May 26, 2020
5867daa
Merge branch 'master' into lambda-docker-code
Jun 3, 2020
882d561
Merge branch 'master' into lambda-docker-code
jogold Jun 4, 2020
ce595a3
start moving to core
jogold Jun 5, 2020
e0112ea
revert .bundle
jogold Jun 5, 2020
587bc82
AssetHashCalculation
jogold Jun 6, 2020
4e4c6d9
Merge branch 'master' into lambda-docker-code
jogold Jun 8, 2020
ea7a48f
lambda README
jogold Jun 8, 2020
dce2bf0
Merge branch 'master' into lambda-docker-code
Jun 8, 2020
706cb48
PR feedback
jogold Jun 8, 2020
8664726
Merge branch 'lambda-docker-code' of github.com:jogold/aws-cdk into l…
jogold Jun 8, 2020
1c0d0b6
will be zipped
jogold Jun 8, 2020
7399c1c
always default to SOURCE
jogold Jun 8, 2020
b72aac6
tests
jogold Jun 8, 2020
78022f6
assetHash
jogold Jun 8, 2020
f646a00
remove source
jogold Jun 8, 2020
913144a
tslint
jogold Jun 8, 2020
1cdf087
correct doc default
jogold Jun 8, 2020
1eb32bd
fix build
jogold Jun 8, 2020
6d6bea4
update integ test
jogold Jun 8, 2020
39092a0
more tests to fix low coverage
jogold Jun 8, 2020
f9b486c
make it clean!
jogold Jun 9, 2020
61a7c19
clean s3-asset
jogold Jun 9, 2020
de3eb47
sourceHash
jogold Jun 9, 2020
2795e87
fix coverage in s3-assets
jogold Jun 9, 2020
3b77e7f
extract to method + if/else
jogold Jun 9, 2020
1ff601d
remove fingerprint() method
jogold Jun 9, 2020
17df113
switch
jogold Jun 9, 2020
96df801
calculateHash(props: AssetStagingProps)
jogold Jun 9, 2020
16fe727
Merge branch 'master' into lambda-docker-code
jogold Jun 9, 2020
8f372ff
Merge branch 'master' into lambda-docker-code
mergify[bot] Jun 9, 2020
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
2 changes: 2 additions & 0 deletions packages/@aws-cdk/assets/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/**
* Common interface for all assets.
*
* @deprecated use `core.IAsset`
*/
export interface IAsset {
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/assets/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './api';
export * from './fs/follow-mode';
export * from './fs/options';
export * from './staging';
export * from './staging';
52 changes: 50 additions & 2 deletions packages/@aws-cdk/aws-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ runtime code.
* `lambda.Code.fromInline(code)` - inline the handle code as a string. This is
limited to supported runtimes and the code cannot exceed 4KiB.
* `lambda.Code.fromAsset(path)` - specify a directory or a .zip file in the local
filesystem which will be zipped and uploaded to S3 before deployment.
filesystem which will be zipped and uploaded to S3 before deployment. See also
[bundling asset code](#Bundling-Asset-Code).

The following example shows how to define a Python function and deploy the code
eladb marked this conversation as resolved.
Show resolved Hide resolved
from the local directory `my-lambda-handler` to it:
Expand Down Expand Up @@ -62,7 +63,7 @@ const fn = new lambda.Function(this, 'MyFunction', {
runtime: lambda.Runtime.NODEJS_10_X,
handler: 'index.handler',
code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')),

fn.role // the Role
```

Expand Down Expand Up @@ -287,6 +288,53 @@ number of times and with different properties. Using `SingletonFunction` here wi
For example, the `LogRetention` construct requires only one single lambda function for all different log groups whose
retention it seeks to manage.

### Bundling Asset Code
When using `lambda.Code.fromAsset(path)` it is possible to bundle the code by running a
command in a Docker container. The asset path will be mounted at `/asset-input`. The
Docker container is responsible for putting content at `/asset-output`. The content at
`/asset-output` will be zipped and used as Lambda code.

Example with Python:
```ts
new lambda.Function(this, 'Function', {
code: lambda.Code.fromAsset(path.join(__dirname, 'my-python-handler'), {
bundling: {
image: lambda.Runtime.PYTHON_3_6.bundlingDockerImage,
command: [
'bash', '-c', `
pip install -r requirements.txt -t /asset-output &&
rsync -r . /asset-output
`,
],
},
}),
runtime: lambda.Runtime.PYTHON_3_6,
handler: 'index.handler',
});
```
Runtimes expose a `bundlingDockerImage` property that points to the [lambci/lambda](https://hub.docker.com/r/lambci/lambda/) build image.

Use `cdk.BundlingDockerImage.fromRegistry(image)` to use an existing image or
`cdk.BundlingDockerImage.fromAsset(path)` to build a specific image:

```ts
import * as cdk from '@aws-cdk/core';

new lambda.Function(this, 'Function', {
code: lambda.Code.fromAsset('/path/to/handler', {
bundling: {
image: cdk.BundlingDockerImage.fromAsset('/path/to/dir/with/DockerFile', {
buildArgs: {
ARG1: 'value1',
},
}),
command: ['my', 'cool', 'command'],
},
}),
// ...
});
```

### Language-specific APIs
Language-specific higher level constructs are provided in separate modules:

Expand Down
11 changes: 11 additions & 0 deletions packages/@aws-cdk/aws-lambda/lib/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { BundlingDockerImage } from '@aws-cdk/core';

export interface LambdaRuntimeProps {
/**
* Whether the ``ZipFile`` (aka inline code) property can be used with this runtime.
Expand Down Expand Up @@ -154,10 +156,19 @@ export class Runtime {
*/
public readonly family?: RuntimeFamily;

/**
* The bundling Docker image for this runtime.
* Points to the lambci/lambda build image for this runtime.
*
* @see https://hub.docker.com/r/lambci/lambda/
*/
public readonly bundlingDockerImage: BundlingDockerImage;

constructor(name: string, family?: RuntimeFamily, props: LambdaRuntimeProps = { }) {
this.name = name;
this.supportsInlineCode = !!props.supportsInlineCode;
this.family = family;
this.bundlingDockerImage = BundlingDockerImage.fromRegistry(`lambci/lambda:build-${name}`);

Runtime.ALL.push(this);
}
Expand Down
113 changes: 113 additions & 0 deletions packages/@aws-cdk/aws-lambda/test/integ.bundling.expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"Resources": {
"FunctionServiceRole675BB04A": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
]
]
}
]
}
},
"Function76856677": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "AssetParameters0ccf37fa0b92d4598d010192eb994040c2e22cc6b12270736d323437817112cdS3Bucket6365D8AA"
},
"S3Key": {
"Fn::Join": [
"",
[
{
"Fn::Select": [
0,
{
"Fn::Split": [
"||",
{
"Ref": "AssetParameters0ccf37fa0b92d4598d010192eb994040c2e22cc6b12270736d323437817112cdS3VersionKey14A1DBA7"
}
]
}
]
},
{
"Fn::Select": [
1,
{
"Fn::Split": [
"||",
{
"Ref": "AssetParameters0ccf37fa0b92d4598d010192eb994040c2e22cc6b12270736d323437817112cdS3VersionKey14A1DBA7"
}
]
}
]
}
]
]
}
},
"Handler": "index.handler",
"Role": {
"Fn::GetAtt": [
"FunctionServiceRole675BB04A",
"Arn"
]
},
"Runtime": "python3.6"
},
"DependsOn": [
"FunctionServiceRole675BB04A"
]
}
},
"Parameters": {
"AssetParameters0ccf37fa0b92d4598d010192eb994040c2e22cc6b12270736d323437817112cdS3Bucket6365D8AA": {
"Type": "String",
"Description": "S3 bucket for asset \"0ccf37fa0b92d4598d010192eb994040c2e22cc6b12270736d323437817112cd\""
},
"AssetParameters0ccf37fa0b92d4598d010192eb994040c2e22cc6b12270736d323437817112cdS3VersionKey14A1DBA7": {
"Type": "String",
"Description": "S3 key for asset version \"0ccf37fa0b92d4598d010192eb994040c2e22cc6b12270736d323437817112cd\""
},
"AssetParameters0ccf37fa0b92d4598d010192eb994040c2e22cc6b12270736d323437817112cdArtifactHashEEC2ED67": {
"Type": "String",
"Description": "Artifact hash for asset \"0ccf37fa0b92d4598d010192eb994040c2e22cc6b12270736d323437817112cd\""
}
},
"Outputs": {
"FunctionArn": {
"Value": {
"Fn::GetAtt": [
"Function76856677",
"Arn"
]
}
}
}
}
42 changes: 42 additions & 0 deletions packages/@aws-cdk/aws-lambda/test/integ.bundling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { App, CfnOutput, Construct, Stack, StackProps } from '@aws-cdk/core';
import * as path from 'path';
import * as lambda from '../lib';

/**
* Stack verification steps:
* * aws cloudformation describe-stacks --stack-name cdk-integ-lambda-bundling --query Stacks[0].Outputs[0].OutputValue
* * aws lambda invoke --function-name <output from above> response.json
* * cat response.json
* The last command should show '200'
*/
class TestStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);

const assetPath = path.join(__dirname, 'python-lambda-handler');
const fn = new lambda.Function(this, 'Function', {
code: lambda.Code.fromAsset(assetPath, {
bundling: {
image: lambda.Runtime.PYTHON_3_6.bundlingDockerImage,
command: [
'bash', '-c', [
'rsync -r . /asset-output',
'cd /asset-output',
'pip install -r requirements.txt -t .',
].join(' && '),
],
},
}),
runtime: lambda.Runtime.PYTHON_3_6,
handler: 'index.handler',
});

new CfnOutput(this, 'FunctionArn', {
value: fn.functionArn,
});
}
}

const app = new App();
new TestStack(app, 'cdk-integ-lambda-bundling');
app.synth();
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import requests

def handler(event, context):
r = requests.get('https://aws.amazon.com')

print(r.status_code)

return r.status_code
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
requests==2.23.0
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-s3-assets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ The following examples grants an IAM group read permissions on an asset:

[Example of granting read access to an asset](./test/integ.assets.permissions.lit.ts)

The following example uses custom asset bundling to convert a markdown file to html:
[Example of using asset bundling](./test/integ.assets.bundling.lit.ts)

## How does it work?

When an asset is defined in a construct, a construct metadata entry
Expand Down
24 changes: 17 additions & 7 deletions packages/@aws-cdk/aws-s3-assets/lib/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import * as cdk from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';
import * as fs from 'fs';
import * as path from 'path';
import { toSymlinkFollow } from './compat';

const ARCHIVE_EXTENSIONS = [ '.zip', '.jar' ];

export interface AssetOptions extends assets.CopyOptions {

export interface AssetOptions extends assets.CopyOptions, cdk.AssetOptions {
/**
* A list of principals that should be able to read this asset from S3.
* You can use `asset.grantRead(principal)` to grant read permissions later.
Expand All @@ -30,7 +30,7 @@ export interface AssetOptions extends assets.CopyOptions {
* @default - automatically calculate source hash based on the contents
* of the source file or directory.
*
* @experimental
* @deprecated see `assetHash` and `assetHashType`
*/
readonly sourceHash?: string;
}
Expand All @@ -50,7 +50,7 @@ export interface AssetProps extends AssetOptions {
* An asset represents a local file or directory, which is automatically uploaded to S3
* and then can be referenced within a CDK application.
*/
export class Asset extends cdk.Construct implements assets.IAsset {
export class Asset extends cdk.Construct implements cdk.IAsset {
/**
* Attribute that represents the name of the bucket this asset exists in.
*/
Expand Down Expand Up @@ -98,18 +98,28 @@ export class Asset extends cdk.Construct implements assets.IAsset {
*/
public readonly isZipArchive: boolean;

/**
* A cryptographic hash of the asset.
*
* @deprecated see `assetHash`
*/
public readonly sourceHash: string;

public readonly assetHash: string;

constructor(scope: cdk.Construct, id: string, props: AssetProps) {
super(scope, id);

// stage the asset source (conditionally).
const staging = new assets.Staging(this, 'Stage', {
const staging = new cdk.AssetStaging(this, 'Stage', {
...props,
sourcePath: path.resolve(props.path),
follow: toSymlinkFollow(props.follow),
assetHash: props.assetHash ?? props.sourceHash,
});

this.sourceHash = props.sourceHash || staging.sourceHash;
this.assetHash = staging.assetHash;
this.sourceHash = this.assetHash;

this.assetPath = staging.stagedPath;

Expand All @@ -136,7 +146,7 @@ export class Asset extends cdk.Construct implements assets.IAsset {

this.bucket = s3.Bucket.fromBucketName(this, 'AssetBucket', this.s3BucketName);

for (const reader of (props.readers || [])) {
for (const reader of (props.readers ?? [])) {
this.grantRead(reader);
}
}
Expand Down
17 changes: 17 additions & 0 deletions packages/@aws-cdk/aws-s3-assets/lib/compat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { FollowMode } from '@aws-cdk/assets';
import { SymlinkFollowMode } from '@aws-cdk/core';

export function toSymlinkFollow(follow?: FollowMode): SymlinkFollowMode | undefined {
if (!follow) {
return undefined;
}

switch (follow) {
case FollowMode.NEVER: return SymlinkFollowMode.NEVER;
case FollowMode.ALWAYS: return SymlinkFollowMode.ALWAYS;
case FollowMode.BLOCK_EXTERNAL: return SymlinkFollowMode.BLOCK_EXTERNAL;
case FollowMode.EXTERNAL: return SymlinkFollowMode.EXTERNAL;
default:
throw new Error(`unknown follow mode: ${follow}`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM alpine

RUN apk add markdown
11 changes: 11 additions & 0 deletions packages/@aws-cdk/aws-s3-assets/test/compat.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { FollowMode } from '@aws-cdk/assets';
import { SymlinkFollowMode } from '@aws-cdk/core';
import { toSymlinkFollow } from '../lib/compat';

test('FollowMode compatibility', () => {
expect(toSymlinkFollow(undefined)).toBeUndefined();
expect(toSymlinkFollow(FollowMode.ALWAYS)).toBe(SymlinkFollowMode.ALWAYS);
expect(toSymlinkFollow(FollowMode.BLOCK_EXTERNAL)).toBe(SymlinkFollowMode.BLOCK_EXTERNAL);
expect(toSymlinkFollow(FollowMode.EXTERNAL)).toBe(SymlinkFollowMode.EXTERNAL);
expect(toSymlinkFollow(FollowMode.NEVER)).toBe(SymlinkFollowMode.NEVER);
});
Loading