Skip to content

Commit 4e02566

Browse files
authored
feat(ecr-assets): Support cache-from and cache-to flags (#24024)
This adds the `--cache-from` and `--cache-to` flag options. --- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md?rgh-link-date=2022-12-09T23%3A48%3A14Z) ### Adding new Construct Runtime Dependencies: * [ ] This PR adds new construct runtime dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/?rgh-link-date=2022-12-09T23%3A48%3A14Z#adding-construct-runtime-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md?rgh-link-date=2022-12-09T23%3A48%3A14Z)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
1 parent 0e9f2b9 commit 4e02566

File tree

17 files changed

+366
-5
lines changed

17 files changed

+366
-5
lines changed

packages/@aws-cdk/aws-ecr-assets/README.md

+12
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ const asset = new DockerImageAsset(this, 'MyBuildImage', {
121121
})
122122
```
123123

124+
You can optionally pass cache from and cache to options to cache images:
125+
126+
```ts
127+
import { DockerImageAsset, Platform } from '@aws-cdk/aws-ecr-assets';
128+
129+
const asset = new DockerImageAsset(this, 'MyBuildImage', {
130+
directory: path.join(__dirname, 'my-image'),
131+
cacheFrom: [{ type: 'registry', params: { ref: 'ghcr.io/myorg/myimage:cache' }}],
132+
cacheTo: { type: 'registry', params: { ref: 'ghcr.io/myorg/myimage:cache', mode: 'max', compression: 'zstd' }}
133+
})
134+
```
135+
124136
## Images from Tarball
125137

126138
Images are loaded from a local tarball, uploaded to ECR by the CDK toolkit and/or your app's CI-CD pipeline, and can be

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

+54
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,28 @@ export interface DockerImageAssetInvalidationOptions {
148148
readonly outputs?: boolean;
149149
}
150150

151+
/**
152+
* Options for configuring the Docker cache backend
153+
*/
154+
export interface DockerCacheOption {
155+
/**
156+
* The type of cache to use.
157+
* Refer to https://docs.docker.com/build/cache/backends/ for full list of backends.
158+
* @default - unspecified
159+
*
160+
* @example 'registry'
161+
*/
162+
readonly type: string;
163+
/**
164+
* Any parameters to pass into the docker cache backend configuration.
165+
* Refer to https://docs.docker.com/build/cache/backends/ for cache backend configuration.
166+
* @default {} No options provided
167+
*
168+
* @example { ref: `12345678.dkr.ecr.us-west-2.amazonaws.com/cache:${branch}`, mode: "max" }
169+
*/
170+
readonly params?: { [key: string]: string };
171+
}
172+
151173
/**
152174
* Options for DockerImageAsset
153175
*/
@@ -236,6 +258,22 @@ export interface DockerImageAssetOptions extends FingerprintOptions, FileFingerp
236258
* @see https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs
237259
*/
238260
readonly outputs?: string[];
261+
262+
/**
263+
* Cache from options to pass to the `docker build` command.
264+
*
265+
* @default - no cache from options are passed to the build command
266+
* @see https://docs.docker.com/build/cache/backends/
267+
*/
268+
readonly cacheFrom?: DockerCacheOption[];
269+
270+
/**
271+
* Cache to options to pass to the `docker build` command.
272+
*
273+
* @default - no cache to options are passed to the build command
274+
* @see https://docs.docker.com/build/cache/backends/
275+
*/
276+
readonly cacheTo?: DockerCacheOption;
239277
}
240278

241279
/**
@@ -316,6 +354,16 @@ export class DockerImageAsset extends Construct implements IAsset {
316354
*/
317355
private readonly dockerOutputs?: string[];
318356

357+
/**
358+
* Cache from options to pass to the `docker build` command.
359+
*/
360+
private readonly dockerCacheFrom?: DockerCacheOption[];
361+
362+
/**
363+
* Cache to options to pass to the `docker build` command.
364+
*/
365+
private readonly dockerCacheTo?: DockerCacheOption;
366+
319367
/**
320368
* Docker target to build to
321369
*/
@@ -407,6 +455,8 @@ export class DockerImageAsset extends Construct implements IAsset {
407455
this.dockerBuildSecrets = props.buildSecrets;
408456
this.dockerBuildTarget = props.target;
409457
this.dockerOutputs = props.outputs;
458+
this.dockerCacheFrom = props.cacheFrom;
459+
this.dockerCacheTo = props.cacheTo;
410460

411461
const location = stack.synthesizer.addDockerImageAsset({
412462
directoryName: this.assetPath,
@@ -418,6 +468,8 @@ export class DockerImageAsset extends Construct implements IAsset {
418468
networkMode: props.networkMode?.mode,
419469
platform: props.platform?.platform,
420470
dockerOutputs: this.dockerOutputs,
471+
dockerCacheFrom: this.dockerCacheFrom,
472+
dockerCacheTo: this.dockerCacheTo,
421473
});
422474

423475
this.repository = ecr.Repository.fromRepositoryName(this, 'Repository', location.repositoryName);
@@ -456,6 +508,8 @@ export class DockerImageAsset extends Construct implements IAsset {
456508
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY] = this.dockerBuildTarget;
457509
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY] = resourceProperty;
458510
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_OUTPUTS_KEY] = this.dockerOutputs;
511+
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_CACHE_FROM_KEY] = this.dockerCacheFrom;
512+
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_CACHE_TO_KEY] = this.dockerCacheTo;
459513
}
460514

461515
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import { AssetManifest } from '@aws-cdk/cloud-assembly-schema';
4+
import { App, Stack } from '@aws-cdk/core';
5+
import { AssetManifestArtifact, CloudArtifact, CloudAssembly } from '@aws-cdk/cx-api';
6+
import { DockerImageAsset } from '../lib';
7+
8+
describe('build cache', () => {
9+
test('manifest contains cache from options ', () => {
10+
// GIVEN
11+
const app = new App();
12+
const stack = new Stack(app);
13+
const asset = new DockerImageAsset(stack, 'DockerImage6', {
14+
directory: path.join(__dirname, 'demo-image'),
15+
cacheFrom: [{ type: 'registry', params: { image: 'foo' } }],
16+
});
17+
18+
// WHEN
19+
const asm = app.synth();
20+
21+
// THEN
22+
const manifestArtifact = getAssetManifest(asm);
23+
const manifest = readAssetManifest(manifestArtifact);
24+
25+
expect(Object.keys(manifest.dockerImages ?? {}).length).toBe(1);
26+
expect(manifest.dockerImages?.[asset.assetHash]?.source.cacheFrom?.length).toBe(1);
27+
expect(manifest.dockerImages?.[asset.assetHash]?.source.cacheFrom?.[0]).toStrictEqual({
28+
type: 'registry',
29+
params: { image: 'foo' },
30+
});
31+
});
32+
test('manifest contains cache to options ', () => {
33+
// GIVEN
34+
const app = new App();
35+
const stack = new Stack(app);
36+
const asset = new DockerImageAsset(stack, 'DockerImage6', {
37+
directory: path.join(__dirname, 'demo-image'),
38+
cacheTo: { type: 'inline' },
39+
});
40+
41+
// WHEN
42+
const asm = app.synth();
43+
44+
// THEN
45+
const manifestArtifact = getAssetManifest(asm);
46+
const manifest = readAssetManifest(manifestArtifact);
47+
48+
expect(Object.keys(manifest.dockerImages ?? {}).length).toBe(1);
49+
expect(manifest.dockerImages?.[asset.assetHash]?.source.cacheTo).toStrictEqual({
50+
type: 'inline',
51+
});
52+
});
53+
54+
test('manifest does not contain options when not specified', () => {
55+
// GIVEN
56+
const app = new App();
57+
const stack = new Stack(app);
58+
const asset = new DockerImageAsset(stack, 'DockerImage6', {
59+
directory: path.join(__dirname, 'demo-image'),
60+
});
61+
62+
// WHEN
63+
const asm = app.synth();
64+
65+
// THEN
66+
const manifestArtifact = getAssetManifest(asm);
67+
const manifest = readAssetManifest(manifestArtifact);
68+
expect(Object.keys(manifest.dockerImages ?? {}).length).toBe(1);
69+
expect(manifest.dockerImages?.[asset.assetHash]?.source.cacheFrom).toBeUndefined();
70+
expect(manifest.dockerImages?.[asset.assetHash]?.source.cacheTo).toBeUndefined();
71+
});
72+
});
73+
74+
function isAssetManifest(x: CloudArtifact): x is AssetManifestArtifact {
75+
return x instanceof AssetManifestArtifact;
76+
}
77+
78+
function getAssetManifest(asm: CloudAssembly): AssetManifestArtifact {
79+
const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0];
80+
if (!manifestArtifact) {
81+
throw new Error('no asset manifest in assembly');
82+
}
83+
return manifestArtifact;
84+
}
85+
86+
function readAssetManifest(manifestArtifact: AssetManifestArtifact): AssetManifest {
87+
return JSON.parse(fs.readFileSync(manifestArtifact.file, { encoding: 'utf-8' }));
88+
}

packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ-assets-docker.template.json

+5
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@
8181
"Value": {
8282
"Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f"
8383
}
84+
},
85+
"ImageUri6": {
86+
"Value": {
87+
"Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:0a3355be12051c9984bf2b0b2bba4e6ea535968e5b6e7396449701732fe5ed14"
88+
}
8489
}
8590
},
8691
"Parameters": {

packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/tree.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,8 @@
262262
"version": "0.0.0"
263263
}
264264
},
265-
"ImageUri5": {
266-
"id": "ImageUri5",
265+
"ImageUri4": {
266+
"id": "ImageUri4",
267267
"path": "integ-assets-docker/ImageUri5",
268268
"constructInfo": {
269269
"fqn": "@aws-cdk/core.CfnOutput",

packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.ts

+7
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,24 @@ const asset5 = new assets.DockerImageAsset(stack, 'DockerImage5', {
3131
},
3232
});
3333

34+
const asset6 = new assets.DockerImageAsset(stack, 'DockerImage6', {
35+
directory: path.join(__dirname, 'demo-image'),
36+
cacheTo: { type: 'inline' },
37+
});
38+
3439
const user = new iam.User(stack, 'MyUser');
3540
asset.repository.grantPull(user);
3641
asset2.repository.grantPull(user);
3742
asset3.repository.grantPull(user);
3843
asset4.repository.grantPull(user);
3944
asset5.repository.grantPull(user);
45+
asset6.repository.grantPull(user);
4046

4147
new cdk.CfnOutput(stack, 'ImageUri', { value: asset.imageUri });
4248
new cdk.CfnOutput(stack, 'ImageUri2', { value: asset2.imageUri });
4349
new cdk.CfnOutput(stack, 'ImageUri3', { value: asset3.imageUri });
4450
new cdk.CfnOutput(stack, 'ImageUri4', { value: asset4.imageUri });
4551
new cdk.CfnOutput(stack, 'ImageUri5', { value: asset5.imageUri });
52+
new cdk.CfnOutput(stack, 'ImageUri6', { value: asset6.imageUri });
4653

4754
app.synth();

packages/@aws-cdk/cloud-assembly-schema/lib/assets/docker-image-asset.ts

+38
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,22 @@ export interface DockerImageSource {
9797
* @see https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs
9898
*/
9999
readonly dockerOutputs?: string[];
100+
101+
/**
102+
* Cache from options to pass to the `docker build` command.
103+
*
104+
* @default - no cache from options are passed to the build command
105+
* @see https://docs.docker.com/build/cache/backends/
106+
*/
107+
readonly cacheFrom?: DockerCacheOption[];
108+
109+
/**
110+
* Cache to options to pass to the `docker build` command.
111+
*
112+
* @default - no cache to options are passed to the build command
113+
* @see https://docs.docker.com/build/cache/backends/
114+
*/
115+
readonly cacheTo?: DockerCacheOption;
100116
}
101117

102118
/**
@@ -113,3 +129,25 @@ export interface DockerImageDestination extends AwsDestination {
113129
*/
114130
readonly imageTag: string;
115131
}
132+
133+
/**
134+
* Options for configuring the Docker cache backend
135+
*/
136+
export interface DockerCacheOption {
137+
/**
138+
* The type of cache to use.
139+
* Refer to https://docs.docker.com/build/cache/backends/ for full list of backends.
140+
* @default - unspecified
141+
*
142+
* @example 'registry'
143+
*/
144+
readonly type: string;
145+
/**
146+
* Any parameters to pass into the docker cache backend configuration.
147+
* Refer to https://docs.docker.com/build/cache/backends/ for cache backend configuration.
148+
* @default {} No options provided
149+
*
150+
* @example { ref: `12345678.dkr.ecr.us-west-2.amazonaws.com/cache:${branch}`, mode: "max" }
151+
*/
152+
readonly params?: { [key: string]: string };
153+
}

packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts

+38
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,28 @@ export interface Tag {
7171
readonly value: string
7272
}
7373

74+
/**
75+
* Options for configuring the Docker cache backend
76+
*/
77+
export interface ContainerImageAssetCacheOption {
78+
/**
79+
* The type of cache to use.
80+
* Refer to https://docs.docker.com/build/cache/backends/ for full list of backends.
81+
* @default - unspecified
82+
*
83+
* @example 'registry'
84+
*/
85+
readonly type: string;
86+
/**
87+
* Any parameters to pass into the docker cache backend configuration.
88+
* Refer to https://docs.docker.com/build/cache/backends/ for cache backend configuration.
89+
* @default {} No options provided
90+
*
91+
* @example { ref: `12345678.dkr.ecr.us-west-2.amazonaws.com/cache:${branch}`, mode: "max" }
92+
*/
93+
readonly params?: { [key: string]: string };
94+
}
95+
7496
/**
7597
* Metadata Entry spec for container images.
7698
*/
@@ -160,6 +182,22 @@ export interface ContainerImageAssetMetadataEntry extends BaseAssetMetadataEntry
160182
* @see https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs
161183
*/
162184
readonly outputs?: string[];
185+
186+
/**
187+
* Cache from options to pass to the `docker build` command.
188+
*
189+
* @default - no cache from options are passed to the build command
190+
* @see https://docs.docker.com/build/cache/backends/
191+
*/
192+
readonly cacheFrom?: ContainerImageAssetCacheOption[];
193+
194+
/**
195+
* Cache to options to pass to the `docker build` command.
196+
*
197+
* @default - no cache to options are passed to the build command
198+
* @see https://docs.docker.com/build/cache/backends/
199+
*/
200+
readonly cacheTo?: ContainerImageAssetCacheOption;
163201
}
164202

165203
/**

packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json

+31
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,40 @@
176176
"items": {
177177
"type": "string"
178178
}
179+
},
180+
"cacheFrom": {
181+
"description": "Cache from options to pass to the `docker build` command. (Default - no cache from options are passed to the build command)",
182+
"type": "array",
183+
"items": {
184+
"$ref": "#/definitions/DockerCacheOption"
185+
}
186+
},
187+
"cacheTo": {
188+
"description": "Cache to options to pass to the `docker build` command. (Default - no cache to options are passed to the build command)",
189+
"$ref": "#/definitions/DockerCacheOption"
179190
}
180191
}
181192
},
193+
"DockerCacheOption": {
194+
"description": "Options for configuring the Docker cache backend",
195+
"type": "object",
196+
"properties": {
197+
"type": {
198+
"description": "The type of cache to use.\nRefer to https://docs.docker.com/build/cache/backends/ for full list of backends. (Default - unspecified)",
199+
"type": "string"
200+
},
201+
"params": {
202+
"description": "Any parameters to pass into the docker cache backend configuration.\nRefer to https://docs.docker.com/build/cache/backends/ for cache backend configuration. (Default {} No options provided)",
203+
"type": "object",
204+
"additionalProperties": {
205+
"type": "string"
206+
}
207+
}
208+
},
209+
"required": [
210+
"type"
211+
]
212+
},
182213
"DockerImageDestination": {
183214
"description": "Where to publish docker images",
184215
"type": "object",

0 commit comments

Comments
 (0)