Skip to content

Commit d152d61

Browse files
authored
fix(core): support cache-from and cache-to flags in DockerImage (#26337)
In #24024, we added the ability to specify Docker cache flags during ecr-asset builds. However, it was not added to the `DockerImage` class. This PR adds the ability to specify the `--cache-from` and `--cache-to` flag to `DockerImage` builds. This logic was primarily lifted directly from #24024. Fixup of issues from #25925 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 2d9d8d6 commit d152d61

File tree

2 files changed

+72
-2
lines changed

2 files changed

+72
-2
lines changed

packages/aws-cdk-lib/core/lib/bundling.ts

+27-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { spawnSync } from 'child_process';
22
import * as crypto from 'crypto';
33
import { isAbsolute, join } from 'path';
4+
import { DockerCacheOption } from './assets';
45
import { FileSystem } from './fs';
56
import { dockerExec } from './private/asset-staging';
67
import { quiet, reset } from './private/jsii-deprecated';
@@ -239,7 +240,7 @@ export class BundlingDockerImage {
239240
}
240241

241242
/** @param image The Docker image */
242-
protected constructor(public readonly image: string, private readonly _imageHash?: string) {}
243+
protected constructor(public readonly image: string, private readonly _imageHash?: string) { }
243244

244245
/**
245246
* Provides a stable representation of this image for JSON serialization.
@@ -355,6 +356,8 @@ export class DockerImage extends BundlingDockerImage {
355356
...(options.file ? ['-f', join(path, options.file)] : []),
356357
...(options.platform ? ['--platform', options.platform] : []),
357358
...(options.targetStage ? ['--target', options.targetStage] : []),
359+
...(options.cacheFrom ? [...options.cacheFrom.map(cacheFrom => ['--cache-from', this.cacheOptionToFlag(cacheFrom)]).flat()] : []),
360+
...(options.cacheTo ? ['--cache-to', this.cacheOptionToFlag(options.cacheTo)] : []),
358361
...flatten(Object.entries(buildArgs).map(([k, v]) => ['--build-arg', `${k}=${v}`])),
359362
path,
360363
];
@@ -379,6 +382,14 @@ export class DockerImage extends BundlingDockerImage {
379382
return new DockerImage(image);
380383
}
381384

385+
private static cacheOptionToFlag(option: DockerCacheOption): string {
386+
let flag = `type=${option.type}`;
387+
if (option.params) {
388+
flag += ',' + Object.entries(option.params).map(([k, v]) => `${k}=${v}`).join(',');
389+
}
390+
return flag;
391+
}
392+
382393
/** The Docker image */
383394
public readonly image: string;
384395

@@ -602,13 +613,27 @@ export interface DockerBuildOptions {
602613
* @default - Build all stages defined in the Dockerfile
603614
*/
604615
readonly targetStage?: string;
616+
617+
/**
618+
* Cache from options to pass to the `docker build` command.
619+
*
620+
* @default - no cache from args are passed
621+
*/
622+
readonly cacheFrom?: DockerCacheOption[];
623+
624+
/**
625+
* Cache to options to pass to the `docker build` command.
626+
*
627+
* @default - no cache to args are passed
628+
*/
629+
readonly cacheTo?: DockerCacheOption;
605630
}
606631

607632
function flatten(x: string[][]) {
608633
return Array.prototype.concat([], ...x);
609634
}
610635

611-
function isSeLinux() : boolean {
636+
function isSeLinux(): boolean {
612637
if (process.platform != 'linux') {
613638
return false;
614639
}

packages/aws-cdk-lib/core/test/bundling.test.ts

+45
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,51 @@ describe('bundling', () => {
124124
])).toEqual(true);
125125
});
126126

127+
test('bundling with image from asset with cache-to & cache-from', () => {
128+
const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({
129+
status: 0,
130+
stderr: Buffer.from('stderr'),
131+
stdout: Buffer.from('stdout'),
132+
pid: 123,
133+
output: ['stdout', 'stderr'],
134+
signal: null,
135+
});
136+
137+
const imageHash = '123456abcdef';
138+
const fingerprintStub = sinon.stub(FileSystem, 'fingerprint');
139+
fingerprintStub.callsFake(() => imageHash);
140+
const cacheTo = { type: 'local', params: { dest: 'path/to/local/dir' } };
141+
const cacheFrom1 = {
142+
type: 's3', params: { region: 'us-west-2', bucket: 'my-bucket', name: 'foo' },
143+
};
144+
const cacheFrom2 = {
145+
type: 'gha', params: { url: 'https://example.com', token: 'abc123', scope: 'gh-ref-image2' },
146+
};
147+
148+
const options = { cacheTo, cacheFrom: [cacheFrom1, cacheFrom2] };
149+
const image = DockerImage.fromBuild('docker-path', options);
150+
image.run();
151+
152+
const tagHash = crypto.createHash('sha256').update(JSON.stringify({
153+
path: 'docker-path',
154+
...options,
155+
})).digest('hex');
156+
const tag = `cdk-${tagHash}`;
157+
158+
expect(spawnSyncStub.firstCall.calledWith(dockerCmd, [
159+
'build', '-t', tag,
160+
'--cache-from', 'type=s3,region=us-west-2,bucket=my-bucket,name=foo',
161+
'--cache-from', 'type=gha,url=https://example.com,token=abc123,scope=gh-ref-image2',
162+
'--cache-to', 'type=local,dest=path/to/local/dir',
163+
'docker-path',
164+
])).toEqual(true);
165+
166+
expect(spawnSyncStub.secondCall.calledWith(dockerCmd, [
167+
'run', '--rm',
168+
tag,
169+
])).toEqual(true);
170+
});
171+
127172
test('bundling with image from asset with target stage', () => {
128173
const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({
129174
status: 0,

0 commit comments

Comments
 (0)