Skip to content

Commit 35937b6

Browse files
authoredNov 1, 2018
feat: don't upload the same asset multiple times (#1011)
This change implements two asset bandwidth conservation measures: - If a lambda.AssetCode object is reused for multiple Lambdas, the same underyling Asset object will be reused (which leads to the asset data only being uploaded once). - If nonetheless multiple Asset objects are created for the same source data, the data will only be uploaded once and subsequently copied on the server-side to avoid the additional data transfer. Fixes #989.
1 parent 1be3442 commit 35937b6

File tree

5 files changed

+108
-14
lines changed

5 files changed

+108
-14
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"Parameters": {
3+
"SampleAsset1S3Bucket469E18FF": {
4+
"Type": "String",
5+
"Description": "S3 bucket for asset \"aws-cdk-multi-assets/SampleAsset1\""
6+
},
7+
"SampleAsset1S3VersionKey63A628F0": {
8+
"Type": "String",
9+
"Description": "S3 key for asset version \"aws-cdk-multi-assets/SampleAsset1\""
10+
},
11+
"SampleAsset2S3BucketC94C651A": {
12+
"Type": "String",
13+
"Description": "S3 bucket for asset \"aws-cdk-multi-assets/SampleAsset2\""
14+
},
15+
"SampleAsset2S3VersionKey3A7E2CC4": {
16+
"Type": "String",
17+
"Description": "S3 key for asset version \"aws-cdk-multi-assets/SampleAsset2\""
18+
}
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import cdk = require('@aws-cdk/cdk');
2+
import path = require('path');
3+
import assets = require('../lib');
4+
5+
class TestStack extends cdk.Stack {
6+
constructor(parent: cdk.App, name: string, props?: cdk.StackProps) {
7+
super(parent, name, props);
8+
9+
// Check that the same asset added multiple times is
10+
// uploaded and copied.
11+
new assets.FileAsset(this, 'SampleAsset1', {
12+
path: path.join(__dirname, 'file-asset.txt')
13+
});
14+
15+
new assets.FileAsset(this, 'SampleAsset2', {
16+
path: path.join(__dirname, 'file-asset.txt')
17+
});
18+
}
19+
}
20+
21+
const app = new cdk.App();
22+
new TestStack(app, 'aws-cdk-multi-assets');
23+
app.run();

‎packages/@aws-cdk/aws-lambda/lib/code.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,13 @@ export class AssetCode extends Code {
143143
}
144144

145145
public bind(lambda: Func) {
146-
this.asset = new assets.Asset(lambda, 'Code', {
147-
path: this.path,
148-
packaging: this.packaging
149-
});
146+
// If the same AssetCode is used multiple times, retain only the first instantiation.
147+
if (!this.asset) {
148+
this.asset = new assets.Asset(lambda, 'Code', {
149+
path: this.path,
150+
packaging: this.packaging
151+
});
152+
}
150153

151154
if (!this.asset.isZipArchive) {
152155
throw new Error(`Asset must be a .zip file or a directory (${this.path})`);

‎packages/@aws-cdk/aws-lambda/test/test.code.ts

+29
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,35 @@ export = {
3636

3737
// THEN
3838
test.throws(() => defineFunction(fileAsset), /Asset must be a \.zip file or a directory/);
39+
test.done();
40+
},
41+
42+
'only one Asset object gets created even if multiple functions use the same AssetCode'(test: Test) {
43+
// GIVEN
44+
const app = new cdk.App();
45+
const stack = new cdk.Stack(app, 'MyStack');
46+
const directoryAsset = lambda.Code.asset(path.join(__dirname, 'my-lambda-handler'));
47+
48+
// WHEN
49+
new lambda.Function(stack, 'Func1', {
50+
handler: 'foom',
51+
runtime: lambda.Runtime.NodeJS810,
52+
code: directoryAsset
53+
});
54+
55+
new lambda.Function(stack, 'Func2', {
56+
handler: 'foom',
57+
runtime: lambda.Runtime.NodeJS810,
58+
code: directoryAsset
59+
});
60+
61+
// THEN
62+
const synthesized = app.synthesizeStack('MyStack');
63+
64+
// Func1 has an asset, Func2 does not
65+
test.deepEqual(synthesized.metadata['/MyStack/Func1/Code'][0].type, 'aws:cdk:asset');
66+
test.deepEqual(synthesized.metadata['/MyStack/Func2/Code'], undefined);
67+
3968
test.done();
4069
}
4170
}

‎packages/aws-cdk/lib/api/toolkit-info.ts

+29-10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ export interface Uploaded {
2121
}
2222

2323
export class ToolkitInfo {
24+
/**
25+
* A cache of previous uploads done in this session
26+
*/
27+
private readonly previousUploads: {[key: string]: Uploaded} = {};
28+
2429
constructor(private readonly props: {
2530
sdk: SDK,
2631
bucketName: string,
@@ -60,17 +65,31 @@ export class ToolkitInfo {
6065
return { filename, key, changed: false };
6166
}
6267

63-
debug(`${url}: uploading`);
64-
await s3.putObject({
65-
Bucket: bucket,
66-
Key: key,
67-
Body: data,
68-
ContentType: props.contentType
69-
}).promise();
70-
71-
debug(`${url}: upload complete`);
68+
const uploaded = { filename, key, changed: true };
69+
70+
// Upload if it's new or server-side copy if it was already uploaded previously
71+
const previous = this.previousUploads[hash];
72+
if (previous) {
73+
debug(`${url}: copying`);
74+
await s3.copyObject({
75+
Bucket: bucket,
76+
Key: key,
77+
CopySource: `${bucket}/${previous.key}`
78+
}).promise();
79+
debug(`${url}: copy complete`);
80+
} else {
81+
debug(`${url}: uploading`);
82+
await s3.putObject({
83+
Bucket: bucket,
84+
Key: key,
85+
Body: data,
86+
ContentType: props.contentType
87+
}).promise();
88+
debug(`${url}: upload complete`);
89+
this.previousUploads[hash] = uploaded;
90+
}
7291

73-
return { filename, key, changed: true };
92+
return uploaded;
7493
}
7594

7695
}

0 commit comments

Comments
 (0)