Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): support cache-from and cache-to flags in DockerImage #26337

Merged
merged 14 commits into from
Aug 25, 2023
Merged
29 changes: 27 additions & 2 deletions packages/aws-cdk-lib/core/lib/bundling.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { spawnSync } from 'child_process';
import * as crypto from 'crypto';
import { isAbsolute, join } from 'path';
import { DockerCacheOption } from './assets';
import { FileSystem } from './fs';
import { dockerExec } from './private/asset-staging';
import { quiet, reset } from './private/jsii-deprecated';
Expand Down Expand Up @@ -239,7 +240,7 @@ export class BundlingDockerImage {
}

/** @param image The Docker image */
protected constructor(public readonly image: string, private readonly _imageHash?: string) {}
protected constructor(public readonly image: string, private readonly _imageHash?: string) { }

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

private static cacheOptionToFlag(option: DockerCacheOption): string {
let flag = `type=${option.type}`;
if (option.params) {
flag += ',' + Object.entries(option.params).map(([k, v]) => `${k}=${v}`).join(',');
}
return flag;
}

/** The Docker image */
public readonly image: string;

Expand Down Expand Up @@ -602,13 +613,27 @@ export interface DockerBuildOptions {
* @default - Build all stages defined in the Dockerfile
*/
readonly targetStage?: string;

/**
* Cache from options to pass to the `docker build` command.
*
* @default - no cache from args are passed
*/
readonly cacheFrom?: DockerCacheOption[];

/**
* Cache to options to pass to the `docker build` command.
*
* @default - no cache to args are passed
*/
readonly cacheTo?: DockerCacheOption;
}

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

function isSeLinux() : boolean {
function isSeLinux(): boolean {
if (process.platform != 'linux') {
return false;
}
Expand Down
44 changes: 44 additions & 0 deletions packages/aws-cdk-lib/core/test/bundling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,50 @@ describe('bundling', () => {
])).toEqual(true);
});

test('bundling with image from asset with cache-to & cache-from', () => {
const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({
status: 0,
stderr: Buffer.from('stderr'),
stdout: Buffer.from('stdout'),
pid: 123,
output: ['stdout', 'stderr'],
signal: null,
});

const imageHash = '123456abcdef';
const fingerprintStub = sinon.stub(FileSystem, 'fingerprint');
fingerprintStub.callsFake(() => imageHash);
const cacheTo = { type: 'local', params: { dest: 'path/to/local/dir' } };
const cacheFrom1 = {
type: 's3', params: { region: 'us-west-2', bucket: 'my-bucket', name: 'foo' },
};
const cacheFrom2 = {
type: 'gha', params: { url: 'https://example.com', token: 'abc123', scope: 'gh-ref-image2' },
};

const image = DockerImage.fromBuild('docker-path', { cacheTo, cacheFrom: [cacheFrom1, cacheFrom2] });
image.run();

const tagHash = crypto.createHash('sha256').update(JSON.stringify({
path: 'docker-path',
cacheTo,
})).digest('hex');
const tag = `cdk-${tagHash}`;

expect(spawnSyncStub.firstCall.calledWith(dockerCmd, [
'build', '-t', tag,
'--cache-to', 'type=local,dest=path/to/local/dir',
'--cache-from', 'type=s3,region=us-west-2,bucket=my-bucket,name=foo',
'--cache-from', 'type=gha,url=https://example.com,token=abc123,scope=gh-ref-image2',
'docker-path',
])).toEqual(true);

expect(spawnSyncStub.secondCall.calledWith(dockerCmd, [
'run', '--rm',
tag,
])).toEqual(true);
});

test('bundling with image from asset with target stage', () => {
const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({
status: 0,
Expand Down