From 65431c3403bf318bbaae31aef080d5c3a83d8f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=9A=80=20Jack?= Date: Tue, 10 Feb 2026 16:57:51 +1100 Subject: [PATCH] feat(cdk-assets-lib): support --build-context flag for docker image builds Add support for `dockerBuildContexts` in the cloud assembly schema and docker build logic. This allows passing additional named build contexts to the `docker build` command via the `--build-context` flag, enabling multi-context Docker builds where files can be sourced from directories, URLs, or other Docker images outside the primary build directory. --- .../cdk-assets-lib/lib/private/docker.ts | 4 ++ .../lib/private/handlers/container-images.ts | 1 + .../cdk-assets-lib/test/docker-images.test.ts | 58 +++++++++++++++++++ .../test/private/docker.test.ts | 24 ++++++++ .../@aws-cdk/cloud-assembly-api/lib/assets.ts | 1 + .../lib/assets/docker-image-asset.ts | 9 +++ .../lib/cloud-assembly/metadata-schema.ts | 7 +++ .../schema/assets.schema.json | 7 +++ .../schema/cloud-assembly.schema.json | 7 +++ .../cloud-assembly-schema/schema/version.json | 4 +- 10 files changed, 120 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/cdk-assets-lib/lib/private/docker.ts b/packages/@aws-cdk/cdk-assets-lib/lib/private/docker.ts index c2794959f..279b4a853 100644 --- a/packages/@aws-cdk/cdk-assets-lib/lib/private/docker.ts +++ b/packages/@aws-cdk/cdk-assets-lib/lib/private/docker.ts @@ -20,6 +20,7 @@ interface BuildOptions { readonly target?: string; readonly file?: string; readonly buildArgs?: Record; + readonly buildContexts?: Record; readonly buildSecrets?: Record; readonly buildSsh?: string; readonly networkMode?: string; @@ -107,6 +108,9 @@ export class Docker { ...flatten( Object.entries(options.buildArgs || {}).map(([k, v]) => ['--build-arg', `${k}=${v}`]), ), + ...flatten( + Object.entries(options.buildContexts || {}).map(([k, v]) => ['--build-context', `${k}=${v}`]), + ), ...flatten( Object.entries(options.buildSecrets || {}).map(([k, v]) => ['--secret', `id=${k},${v}`]), ), diff --git a/packages/@aws-cdk/cdk-assets-lib/lib/private/handlers/container-images.ts b/packages/@aws-cdk/cdk-assets-lib/lib/private/handlers/container-images.ts index 402694ede..e0b2c9dad 100644 --- a/packages/@aws-cdk/cdk-assets-lib/lib/private/handlers/container-images.ts +++ b/packages/@aws-cdk/cdk-assets-lib/lib/private/handlers/container-images.ts @@ -232,6 +232,7 @@ class ContainerImageBuilder { directory: fullPath, tag: localTagName, buildArgs: source.dockerBuildArgs, + buildContexts: source.dockerBuildContexts, buildSecrets: source.dockerBuildSecrets, buildSsh: source.dockerBuildSsh, target: source.dockerBuildTarget, diff --git a/packages/@aws-cdk/cdk-assets-lib/test/docker-images.test.ts b/packages/@aws-cdk/cdk-assets-lib/test/docker-images.test.ts index 921bc31c8..435ceb89d 100644 --- a/packages/@aws-cdk/cdk-assets-lib/test/docker-images.test.ts +++ b/packages/@aws-cdk/cdk-assets-lib/test/docker-images.test.ts @@ -275,6 +275,29 @@ beforeEach(() => { }, }), '/platform-arm64/cdk.out/dockerdir/Dockerfile': 'FROM scratch', + '/build-contexts/cdk.out/assets.json': JSON.stringify({ + version: Manifest.version(), + dockerImages: { + theAsset: { + source: { + directory: 'dockerdir', + dockerBuildContexts: { + mycontext: '../context', + alpine: 'docker-image://alpine:latest', + }, + }, + destinations: { + theDestination: { + region: 'us-north-50', + assumeRoleArn: 'arn:aws:role', + repositoryName: 'repo', + imageTag: 'nopqr', + }, + }, + }, + }, + }), + '/build-contexts/cdk.out/dockerdir/Dockerfile': 'FROM scratch', }); aws = new MockAws(); mockEcr.on(DescribeImagesCommand).rejects(err); @@ -610,6 +633,41 @@ describe('with a complete manifest', () => { expectAllSpawns(); expect(true).toBeTruthy(); // Expect no exception, satisfy linter }); + + test('build with buildContexts option', async () => { + pub = new AssetPublishing(AssetManifest.fromPath(mockfs.path('/build-contexts/cdk.out')), { + aws, + }); + const buildContextsDockerpath = '/build-contexts/cdk.out/dockerdir'; + + const expectAllSpawns = mockSpawn( + { + commandLine: ['docker', 'login', '--username', 'user', '--password-stdin', 'proxy.com'], + }, + { commandLine: ['docker', 'inspect', 'cdkasset-theasset'], exitCode: 1 }, + { + commandLine: [ + 'docker', + 'build', + '--build-context', + 'mycontext=../context', + '--build-context', + 'alpine=docker-image://alpine:latest', + '--tag', + 'cdkasset-theasset', + '.', + ], + cwd: buildContextsDockerpath, + }, + { commandLine: ['docker', 'tag', 'cdkasset-theasset', '12345.amazonaws.com/repo:nopqr'] }, + { commandLine: ['docker', 'push', '12345.amazonaws.com/repo:nopqr'] }, + ); + + await pub.publish(); + + expectAllSpawns(); + expect(true).toBeTruthy(); // Expect no exception, satisfy linter + }); }); describe('external assets', () => { diff --git a/packages/@aws-cdk/cdk-assets-lib/test/private/docker.test.ts b/packages/@aws-cdk/cdk-assets-lib/test/private/docker.test.ts index 291830754..284065b0b 100644 --- a/packages/@aws-cdk/cdk-assets-lib/test/private/docker.test.ts +++ b/packages/@aws-cdk/cdk-assets-lib/test/private/docker.test.ts @@ -115,5 +115,29 @@ describe('Docker', () => { }), ); }); + + test('includes --build-context flags when buildContexts are provided', async () => { + const spy = makeShellExecuteMock(() => undefined); + + await docker.build({ + directory: 'foo', + tag: 'bar', + buildContexts: { + mycontext: '../context', + alpine: 'docker-image://alpine:latest', + }, + }); + + expect(spy.mock.calls[0][0]).toEqual([ + 'build', + '--build-context', + 'mycontext=../context', + '--build-context', + 'alpine=docker-image://alpine:latest', + '--tag', + 'bar', + '.', + ]); + }); }); }); diff --git a/packages/@aws-cdk/cloud-assembly-api/lib/assets.ts b/packages/@aws-cdk/cloud-assembly-api/lib/assets.ts index ad67cfec6..09fcc5e14 100644 --- a/packages/@aws-cdk/cloud-assembly-api/lib/assets.ts +++ b/packages/@aws-cdk/cloud-assembly-api/lib/assets.ts @@ -12,6 +12,7 @@ export const ASSET_RESOURCE_METADATA_ENABLED_CONTEXT = 'aws:cdk:enable-asset-met export const ASSET_RESOURCE_METADATA_PATH_KEY = 'aws:asset:path'; export const ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY = 'aws:asset:dockerfile-path'; export const ASSET_RESOURCE_METADATA_DOCKER_BUILD_ARGS_KEY = 'aws:asset:docker-build-args'; +export const ASSET_RESOURCE_METADATA_DOCKER_BUILD_CONTEXTS_KEY = 'aws:asset:docker-build-contexts'; export const ASSET_RESOURCE_METADATA_DOCKER_BUILD_SECRETS_KEY = 'aws:asset:docker-build-secrets'; export const ASSET_RESOURCE_METADATA_DOCKER_BUILD_SSH_KEY = 'aws:asset:docker-build-ssh'; export const ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY = 'aws:asset:docker-build-target'; diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/assets/docker-image-asset.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/assets/docker-image-asset.ts index c91dd63ab..e0bfa2dbc 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/assets/docker-image-asset.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/assets/docker-image-asset.ts @@ -70,6 +70,15 @@ export interface DockerImageSource { */ readonly dockerBuildArgs?: { [name: string]: string }; + /** + * Additional build contexts + * + * Only allowed when `directory` is set. + * + * @default - No additional build contexts + */ + readonly dockerBuildContexts?: { [name: string]: string }; + /** * SSH agent socket or keys * diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts index f2b6affb7..afd138e76 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts @@ -161,6 +161,13 @@ export interface ContainerImageAssetMetadataEntry extends BaseAssetMetadataEntry */ readonly buildArgs?: { [key: string]: string }; + /** + * Build contexts to pass to the `docker build` command + * + * @default no build contexts are passed + */ + readonly buildContexts?: { [key: string]: string }; + /** * SSH agent socket or keys to pass to the `docker build` command * diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json b/packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json index c1f88aead..865da65b4 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json @@ -168,6 +168,13 @@ "type": "string" } }, + "dockerBuildContexts": { + "description": "Additional build contexts\n\nOnly allowed when `directory` is set. (Default - No additional build contexts)", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "dockerBuildSsh": { "description": "SSH agent socket or keys\n\nRequires building with docker buildkit. (Default - No ssh flag is set)", "type": "string" diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json index 2e3e5fa40..a1cfe27e1 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json @@ -232,6 +232,13 @@ "type": "string" } }, + "buildContexts": { + "description": "Build contexts to pass to the `docker build` command (Default no build contexts are passed)", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "buildSsh": { "description": "SSH agent socket or keys to pass to the `docker build` command (Default no ssh arg is passed)", "type": "string" diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/version.json b/packages/@aws-cdk/cloud-assembly-schema/schema/version.json index 5b7c6a5df..eaf2e3f87 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/version.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/version.json @@ -1,5 +1,5 @@ { - "schemaHash": "22c511a4ddd185761b8d56ac21d48c8384873ffe4b953b3567654746f8dd26f1", + "schemaHash": "25d8ad154190878ec8a9e970fa21a0e182fa834304913d43be86f9b0613271f5", "$comment": "Do not hold back the version on additions: jsonschema validation of the manifest by the consumer will trigger errors on unexpected fields.", - "revision": 52 + "revision": 53 } \ No newline at end of file