Skip to content

Commit 8484114

Browse files
jogoldmergify[bot]
authored andcommitted
fix(assets): docker asset versions are pushed to separate repositories (#4537)
* fix(core): upload ecr assets to separate repositories Derive repository name from asset id, not asset source hash. We need to derive the repository name from the asset id otherwise the CDK will create a new repository each time the image changes (which will make layer caching useless). Fixes #4535 * move default for repositoryName to DockerImageAsset * repositoryName * add test * allow breaking change for addDockerImageAsset * non breaking * add test for addDockerImageAsset * more tests * remove useless diff * simplify * test.assets.ts
1 parent 406dc8e commit 8484114

File tree

6 files changed

+152
-6
lines changed

6 files changed

+152
-6
lines changed

packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export class DockerImageAsset extends Construct implements assets.IAsset {
9696
directoryName: staging.stagedPath,
9797
dockerBuildArgs: props.buildArgs,
9898
dockerBuildTarget: props.target,
99-
repositoryName: props.repositoryName,
99+
repositoryName: props.repositoryName || `cdk/${this.node.uniqueId.replace(/[:/]/g, '-').toLowerCase()}`,
100100
sourceHash: staging.sourceHash
101101
});
102102

packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { expect, haveResource, SynthUtils } from '@aws-cdk/assert';
22
import iam = require('@aws-cdk/aws-iam');
3-
import { App, Lazy, Stack } from '@aws-cdk/core';
3+
import { App, Construct, Lazy, Resource, Stack } from '@aws-cdk/core';
4+
import { ASSET_METADATA } from '@aws-cdk/cx-api';
45
import fs = require('fs');
56
import { Test } from 'nodeunit';
67
import path = require('path');
@@ -28,6 +29,27 @@ export = {
2829
test.done();
2930
},
3031

32+
'repository name is derived from node unique id'(test: Test) {
33+
// GIVEN
34+
const stack = new Stack();
35+
class CoolConstruct extends Resource {
36+
constructor(scope: Construct, id: string) {
37+
super(scope, id);
38+
}
39+
}
40+
const coolConstruct = new CoolConstruct(stack, 'CoolConstruct');
41+
42+
// WHEN
43+
new DockerImageAsset(coolConstruct, 'Image', {
44+
directory: path.join(__dirname, 'demo-image'),
45+
});
46+
47+
// THEN
48+
const assetMetadata = stack.node.metadata.find(({ type }) => type === ASSET_METADATA);
49+
test.deepEqual(assetMetadata && assetMetadata.data.repositoryName, 'cdk/coolconstructimage78ab38fc');
50+
test.done();
51+
},
52+
3153
'with build args'(test: Test) {
3254
// GIVEN
3355
const stack = new Stack();
@@ -41,7 +63,7 @@ export = {
4163
});
4264

4365
// THEN
44-
const assetMetadata = stack.node.metadata.find(({ type }) => type === 'aws:cdk:asset');
66+
const assetMetadata = stack.node.metadata.find(({ type }) => type === ASSET_METADATA);
4567
test.deepEqual(assetMetadata && assetMetadata.data.buildArgs, { a: 'b' });
4668
test.done();
4769
},
@@ -60,7 +82,7 @@ export = {
6082
});
6183

6284
// THEN
63-
const assetMetadata = stack.node.metadata.find(({ type }) => type === 'aws:cdk:asset');
85+
const assetMetadata = stack.node.metadata.find(({ type }) => type === ASSET_METADATA);
6486
test.deepEqual(assetMetadata && assetMetadata.data.target, 'a-target');
6587
test.done();
6688
},

packages/@aws-cdk/core/lib/stack.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ export class Stack extends Construct implements ITaggable {
532532
params = new DockerImageAssetParameters(this.assetParameters, asset.sourceHash);
533533

534534
const metadata: cxapi.ContainerImageAssetMetadataEntry = {
535-
id: this.node.uniqueId,
535+
id: asset.sourceHash,
536536
packaging: 'container-image',
537537
path: asset.directoryName,
538538
sourceHash: asset.sourceHash,
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import cxapi = require('@aws-cdk/cx-api');
2+
import { Test } from 'nodeunit';
3+
import { FileAssetPackaging, Stack } from '../lib';
4+
import { toCloudFormation } from './util';
5+
6+
export = {
7+
'addFileAsset correctly sets metadata and creates S3 parameters'(test: Test) {
8+
// GIVEN
9+
const stack = new Stack();
10+
11+
// WHEN
12+
stack.addFileAsset({
13+
fileName: 'file-name',
14+
packaging: FileAssetPackaging.ZIP_DIRECTORY,
15+
sourceHash: 'source-hash'
16+
});
17+
18+
// THEN
19+
const assetMetadata = stack.node.metadata.find(({ type }) => type === cxapi.ASSET_METADATA);
20+
21+
test.equal(assetMetadata && assetMetadata.data.path, 'file-name');
22+
test.equal(assetMetadata && assetMetadata.data.id, 'source-hash');
23+
test.equal(assetMetadata && assetMetadata.data.packaging, FileAssetPackaging.ZIP_DIRECTORY);
24+
test.equal(assetMetadata && assetMetadata.data.sourceHash, 'source-hash');
25+
26+
test.deepEqual(toCloudFormation(stack), {
27+
Parameters: {
28+
AssetParameterssourcehashS3BucketE6E91E3E: {
29+
Type: 'String',
30+
Description: 'S3 bucket for asset "source-hash"'
31+
},
32+
AssetParameterssourcehashS3VersionKeyAC4157C3: {
33+
Type: 'String',
34+
Description: 'S3 key for asset version "source-hash"'
35+
},
36+
AssetParameterssourcehashArtifactHashADBAE418: {
37+
Type: 'String',
38+
Description: 'Artifact hash for asset "source-hash"'
39+
}
40+
}
41+
});
42+
43+
test.done();
44+
45+
},
46+
47+
'addDockerImageAsset correctly sets metadata and creates an ECR parameter'(test: Test) {
48+
// GIVEN
49+
const stack = new Stack();
50+
51+
// WHEN
52+
stack.addDockerImageAsset({
53+
sourceHash: 'source-hash',
54+
directoryName: 'directory-name',
55+
repositoryName: 'repository-name'
56+
});
57+
58+
// THEN
59+
const assetMetadata = stack.node.metadata.find(({ type }) => type === cxapi.ASSET_METADATA);
60+
61+
test.equal(assetMetadata && assetMetadata.data.packaging, 'container-image');
62+
test.equal(assetMetadata && assetMetadata.data.path, 'directory-name');
63+
test.equal(assetMetadata && assetMetadata.data.sourceHash, 'source-hash');
64+
test.equal(assetMetadata && assetMetadata.data.repositoryName, 'repository-name');
65+
66+
test.deepEqual(toCloudFormation(stack), {
67+
Parameters: {
68+
AssetParameterssourcehashImageName3B572B12: {
69+
Type: 'String',
70+
Description: 'ECR repository name and tag for asset "source-hash"'
71+
}
72+
}
73+
});
74+
75+
test.done();
76+
},
77+
};

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export class ToolkitInfo {
108108
public async prepareEcrRepository(asset: cxapi.ContainerImageAssetMetadataEntry): Promise<EcrRepositoryInfo> {
109109
const ecr = await this.props.sdk.ecr(this.props.environment.account, this.props.environment.region, Mode.ForWriting);
110110
let repositoryName;
111-
if ( asset.repositoryName ) {
111+
if (asset.repositoryName) {
112112
// Repository name provided by user
113113
repositoryName = asset.repositoryName;
114114
} else {

packages/aws-cdk/test/test.docker.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,53 @@ export = {
5555
test.done();
5656
},
5757

58+
async 'derives repository name from asset id'(test: Test) {
59+
// GIVEN
60+
61+
let createdName;
62+
63+
const sdk = new MockSDK();
64+
sdk.stubEcr({
65+
describeRepositories() {
66+
return { repositories: [] };
67+
},
68+
69+
createRepository(req) {
70+
createdName = req.repositoryName;
71+
72+
// Stop the test so that we don't actually docker build
73+
throw new Error('STOPTEST');
74+
},
75+
});
76+
77+
const toolkit = new ToolkitInfo({
78+
sdk,
79+
bucketName: 'BUCKET_NAME',
80+
bucketEndpoint: 'BUCKET_ENDPOINT',
81+
environment: { name: 'env', account: '1234', region: 'abc' }
82+
});
83+
84+
// WHEN
85+
const asset: cxapi.ContainerImageAssetMetadataEntry = {
86+
id: 'Stack:Construct/ABC123',
87+
imageNameParameter: 'MyParameter',
88+
packaging: 'container-image',
89+
path: '/foo',
90+
sourceHash: '0123456789abcdef',
91+
};
92+
93+
try {
94+
await prepareContainerAsset('.', asset, toolkit, false);
95+
} catch (e) {
96+
if (!/STOPTEST/.test(e.toString())) { throw e; }
97+
}
98+
99+
// THEN
100+
test.deepEqual(createdName, 'cdk/stack-construct-abc123');
101+
102+
test.done();
103+
},
104+
58105
async 'passes the correct target to docker build'(test: Test) {
59106
// GIVEN
60107
const toolkit = new ToolkitInfo({

0 commit comments

Comments
 (0)