Skip to content

Commit

Permalink
feat(assets): enable local tooling scenarios such as lambda debugging
Browse files Browse the repository at this point in the history
Adds CloudFormation resource metadata which enables tools such as SAM
CLI to find local assets used by resources in the template.

See design document under [design/code-asset-metadata.md](./design/code-asset-metadata.md)

Fixes #1432
  • Loading branch information
Elad Ben-Israel committed Dec 27, 2018
1 parent 9eeb8de commit ad1a902
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 13 deletions.
7 changes: 4 additions & 3 deletions design/code-asset-metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ The synthesized `AWS::Lambda::Function` resource will include a "Metadata" entry
}
},
"Metadata": {
"aws:code:property": "Code",
"aws:code:path": "/path/to/handler"
"aws:asset:property": "Code",
"aws:asset:path": "/path/to/handler"
}
}
```

Local debugging tools like SAM CLI will be able to traverse the template and look up the `aws:source-code` metadata
Local debugging tools like SAM CLI will be able to traverse the template and look up the `aws:asset` metadata
entries, and use them to process the template so it will be compatible with their inputs.

22 changes: 22 additions & 0 deletions packages/@aws-cdk/assets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,25 @@ the asset store, it is uploaded during deployment.

Now, when the toolkit deploys the stack, it will set the relevant CloudFormation
Parameters to point to the actual bucket and key for each asset.

## CloudFormation Resource Metadata

> NOTE: This section is relevant for authors of AWS Resource Constructs.
In certain situations, it is desirable for tools to be able to know that a certain CloudFormation
resource is using a local asset. For example, SAM CLI can be used to invoke AWS Lambda functions
locally for debugging purposes.

To enable such use cases, external tools will consult a set of metadata entries on AWS CloudFormation
resources:

- `aws:asset:path` points to the local path of the asset.
- `aws:asset:property` is the name of the resource property where the asset is used

Using these two metadata entries, tools will be able to identify that assets are used
by a certain resource, and enable advanced local experiences.

To add these metadata entries to a resource, use the
`asset.addResourceMetadata(resource, property)` method.

See https://github.com/awslabs/aws-cdk/issues/1432 for more details
19 changes: 19 additions & 0 deletions packages/@aws-cdk/assets/lib/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,25 @@ export class Asset extends cdk.Construct {
}
}

/**
* Adds CloudFormation template metadata to the specified resource with
* information that indicates which resource property is mapped to this
* local asset. This can be used by tools such as SAM CLI to provide local
* experience such as local invocation and debugging of Lambda functions.
*
* @see https://github.com/awslabs/aws-cdk/issues/1432
*
* @param resource The CloudFormation resource which is using this asset.
* @param resourceProperty The property name where this asset is referenced (e.g. "Code" for AWS::Lambda::Function)
*/
public addResourceMetadata(resource: cdk.Resource, resourceProperty: string) {
// tell tools such as SAM CLI that the "Code" property of this resource
// points to a local path in order to enable local invocation of this function.
resource.options.metadata = resource.options.metadata || { };
resource.options.metadata[cxapi.ASSET_RESOURCE_PATH_METADATA] = this.assetPath;
resource.options.metadata[cxapi.ASSET_RESOURCE_PROPERTY_METADATA] = resourceProperty;
}

/**
* Grants read permissions to the principal on the asset's S3 object.
*/
Expand Down
22 changes: 21 additions & 1 deletion packages/@aws-cdk/assets/test/test.asset.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, haveResource } from '@aws-cdk/assert';
import { expect, haveResource, ResourcePart } from '@aws-cdk/assert';
import iam = require('@aws-cdk/aws-iam');
import cdk = require('@aws-cdk/cdk');
import { Test } from 'nodeunit';
Expand Down Expand Up @@ -140,5 +140,25 @@ export = {
test.equal(zipFileAsset.isZipArchive, true);
test.equal(jarFileAsset.isZipArchive, true);
test.done();
},

'addResourceMetadata can be used to add CFN metadata to resources'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const location = path.join(__dirname, 'sample-asset-directory');
const resource = new cdk.Resource(stack, 'MyResource', { type: 'My::Resource::Type' });
const asset = new ZipDirectoryAsset(stack, 'MyAsset', { path: location });

// WHEN
asset.addResourceMetadata(resource, 'PropName');

// THEN
expect(stack).to(haveResource('My::Resource::Type', {
Metadata: {
"aws:asset:path": location,
"aws:asset:property": "PropName"
}
}, ResourcePart.CompleteDefinition));
test.done();
}
};
11 changes: 7 additions & 4 deletions packages/@aws-cdk/aws-lambda/lib/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export abstract class Code {
* Called during stack synthesis to render the CodePropery for the
* Lambda function.
*/
public abstract toJSON(): CfnFunction.CodeProperty;
public abstract toJSON(resource: CfnFunction): CfnFunction.CodeProperty;

/**
* Called when the lambda is initialized to allow this object to
Expand All @@ -81,7 +81,7 @@ export class S3Code extends Code {
this.bucketName = bucket.bucketName;
}

public toJSON(): CfnFunction.CodeProperty {
public toJSON(_: CfnFunction): CfnFunction.CodeProperty {
return {
s3Bucket: this.bucketName,
s3Key: this.key,
Expand All @@ -108,7 +108,7 @@ export class InlineCode extends Code {
}
}

public toJSON(): CfnFunction.CodeProperty {
public toJSON(_: CfnFunction): CfnFunction.CodeProperty {
return {
zipFile: this.code
};
Expand Down Expand Up @@ -156,7 +156,10 @@ export class AssetCode extends Code {
}
}

public toJSON(): CfnFunction.CodeProperty {
public toJSON(resource: CfnFunction): CfnFunction.CodeProperty {
// https://github.com/awslabs/aws-cdk/issues/1432
this.asset!.addResourceMetadata(resource, 'Code');

return {
s3Bucket: this.asset!.s3BucketName,
s3Key: this.asset!.s3ObjectKey
Expand Down
15 changes: 10 additions & 5 deletions packages/@aws-cdk/aws-lambda/lib/lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,11 @@ export class Function extends FunctionRef {
*/
private readonly environment?: { [key: string]: any };

/**
* Internal CFN resource.
*/
private readonly resource: CfnFunction;

constructor(parent: cdk.Construct, name: string, props: FunctionProps) {
super(parent, name);

Expand All @@ -241,10 +246,10 @@ export class Function extends FunctionRef {
this.role.addToPolicy(statement);
}

const resource = new CfnFunction(this, 'Resource', {
this.resource = new CfnFunction(this, 'Resource', {
functionName: props.functionName,
description: props.description,
code: new cdk.Token(() => props.code.toJSON()),
code: new cdk.Token(() => props.code.toJSON(this.resource)),
handler: props.handler,
timeout: props.timeout,
runtime: props.runtime.name,
Expand All @@ -256,10 +261,10 @@ export class Function extends FunctionRef {
tracingConfig: this.buildTracingConfig(props)
});

resource.addDependency(this.role);
this.resource.addDependency(this.role);

this.functionName = resource.ref;
this.functionArn = resource.functionArn;
this.functionName = this.resource.ref;
this.functionArn = this.resource.functionArn;
this.handler = props.handler;
this.runtime = props.runtime;

Expand Down
23 changes: 23 additions & 0 deletions packages/@aws-cdk/aws-lambda/test/test.code.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { expect, haveResource, ResourcePart } from '@aws-cdk/assert';
import assets = require('@aws-cdk/assets');
import cdk = require('@aws-cdk/cdk');
import { Test } from 'nodeunit';
Expand Down Expand Up @@ -65,6 +66,28 @@ export = {
test.deepEqual(synthesized.metadata['/MyStack/Func1/Code'][0].type, 'aws:cdk:asset');
test.deepEqual(synthesized.metadata['/MyStack/Func2/Code'], undefined);

test.done();
},

'adds code asset metadata'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const location = path.join(__dirname, 'my-lambda-handler');

// WHEN
new lambda.Function(stack, 'Func1', {
code: lambda.Code.asset(location),
runtime: lambda.Runtime.NodeJS810,
handler: 'foom',
});

// THEN
expect(stack).to(haveResource('AWS::Lambda::Function', {
Metadata: {
"aws:asset:path": location,
"aws:asset:property": "Code"
}
}, ResourcePart.CompleteDefinition));
test.done();
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/cx-api/lib/metadata/assets.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const ASSET_METADATA = 'aws:cdk:asset';
export const ASSET_RESOURCE_PATH_METADATA = 'aws:asset:path';
export const ASSET_RESOURCE_PROPERTY_METADATA = 'aws:asset:property';

export interface FileAssetMetadataEntry {
/**
Expand Down

0 comments on commit ad1a902

Please sign in to comment.