Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions packages/@aws-cdk/core/lib/bundling.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { spawnSync, SpawnSyncOptions } from 'child_process';
import * as crypto from 'crypto';
import { FileSystem } from './fs';

/**
Expand Down Expand Up @@ -108,28 +109,29 @@ export class BundlingDockerImage {
public static fromAsset(path: string, options: DockerBuildOptions = {}) {
const buildArgs = options.buildArgs || {};

// Image tag derived from path and build options
const tagHash = crypto.createHash('sha256').update(JSON.stringify({
path,
...options,
})).digest('hex');
const tag = `cdk-${tagHash}`;

const dockerArgs: string[] = [
'build', '-q',
'build', '-t', tag,
...(options.file ? ['-f', options.file] : []),
...flatten(Object.entries(buildArgs).map(([k, v]) => ['--build-arg', `${k}=${v}`])),
path,
];

const docker = dockerExec(dockerArgs);

const match = docker.stdout.toString().match(/sha256:[a-z0-9]+/);

if (!match) {
throw new Error('Failed to extract image ID from Docker build output');
}
dockerExec(dockerArgs);

// Fingerprints the directory containing the Dockerfile we're building and
// differentiates the fingerprint based on build arguments. We do this so
// we can provide a stable image hash. Otherwise, the image ID will be
// different every time the Docker layer cache is cleared, due primarily to
// timestamps.
const hash = FileSystem.fingerprint(path, { extraHash: JSON.stringify(options) });
return new BundlingDockerImage(match[0], hash);
return new BundlingDockerImage(tag, hash);
}

/** @param image The Docker image */
Expand Down Expand Up @@ -166,13 +168,7 @@ export class BundlingDockerImage {
...command,
];

dockerExec(dockerArgs, {
stdio: [ // show Docker output
'ignore', // ignore stdio
process.stderr, // redirect stdout to stderr
'inherit', // inherit stderr
],
});
dockerExec(dockerArgs);
}

/**
Expand Down Expand Up @@ -303,7 +299,13 @@ function flatten(x: string[][]) {

function dockerExec(args: string[], options?: SpawnSyncOptions) {
const prog = process.env.CDK_DOCKER ?? 'docker';
const proc = spawnSync(prog, args, options);
const proc = spawnSync(prog, args, options ?? {
stdio: [ // show Docker output
'ignore', // ignore stdio
process.stderr, // redirect stdout to stderr
'inherit', // inherit stderr
],
});

if (proc.error) {
throw proc.error;
Expand Down
39 changes: 18 additions & 21 deletions packages/@aws-cdk/core/test/bundling.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as child_process from 'child_process';
import * as crypto from 'crypto';
import * as path from 'path';
import { nodeunitShim, Test } from 'nodeunit-shim';
import * as sinon from 'sinon';
Expand Down Expand Up @@ -46,11 +47,10 @@ nodeunitShim({
},

'bundling with image from asset'(test: Test) {
const imageId = 'sha256:abcdef123456';
const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({
status: 0,
stderr: Buffer.from('stderr'),
stdout: Buffer.from(imageId),
stdout: Buffer.from('stdout'),
pid: 123,
output: ['stdout', 'stderr'],
signal: null,
Expand All @@ -67,33 +67,27 @@ nodeunitShim({
});
image.run();

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

test.ok(spawnSyncStub.firstCall.calledWith('docker', [
'build', '-q',
'build', '-t', tag,
'--build-arg', 'TEST_ARG=cdk-test',
'docker-path',
]));

test.ok(spawnSyncStub.secondCall.calledWith('docker', [
'run', '--rm',
imageId,
tag,
]));
test.done();
},

'throws if image id cannot be extracted from build output'(test: Test) {
sinon.stub(child_process, 'spawnSync').returns({
status: 0,
stderr: Buffer.from('stderr'),
stdout: Buffer.from('stdout'),
pid: 123,
output: ['stdout', 'stderr'],
signal: null,
});

test.throws(() => BundlingDockerImage.fromAsset('docker-path'), /Failed to extract image ID from Docker build output/);
test.done();
},

'throws in case of spawnSync error'(test: Test) {
sinon.stub(child_process, 'spawnSync').returns({
status: 0,
Expand Down Expand Up @@ -133,11 +127,10 @@ nodeunitShim({
},

'BundlerDockerImage json is the bundler image if building an image'(test: Test) {
const imageId = 'sha256:abcdef123456';
sinon.stub(child_process, 'spawnSync').returns({
status: 0,
stderr: Buffer.from('stderr'),
stdout: Buffer.from(imageId),
stdout: Buffer.from('stdout'),
pid: 123,
output: ['stdout', 'stderr'],
signal: null,
Expand All @@ -148,7 +141,11 @@ nodeunitShim({

const image = BundlingDockerImage.fromAsset('docker-path');

test.equals(image.image, imageId);
const tagHash = crypto.createHash('sha256').update(JSON.stringify({
path: 'docker-path',
})).digest('hex');

test.equals(image.image, `cdk-${tagHash}`);
test.equals(image.toJSON(), imageHash);
test.ok(fingerprintStub.calledWith('docker-path', sinon.match({ extraHash: JSON.stringify({}) })));
test.done();
Expand Down