From adc0368dc1f137aeaa4bd92de77028269e3a48f4 Mon Sep 17 00:00:00 2001 From: Joe Flateau Date: Thu, 26 May 2022 09:21:35 -0400 Subject: [PATCH] feat(aws-ecr-assets): support the --platform option when building docker images (#20439) This PR adds support for specifying the desired build platform when building docker images (ie: build an arm64 image on an amd64/x86_64 host). Closes #12472 This PR does NOT touch Lambda builders, only ECR assets. #16770 attempted to implement support for ECR and Lambda but was abandoned. Meanwhile #16858 implemented lambda platform support. This implements the ECR side I have run `yarn integ` ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [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* --- packages/@aws-cdk/aws-ecr-assets/README.md | 12 +++++ .../aws-ecr-assets/lib/image-asset.ts | 46 +++++++++++++++++++ .../Dockerfile | 5 ++ .../index.py | 33 +++++++++++++ .../test/assets-docker.integ.snapshot/cdk.out | 2 +- .../integ-assets-docker.template.json | 42 +++++++++++++++++ .../assets-docker.integ.snapshot/integ.json | 4 +- .../manifest.json | 26 ++++++++++- .../assets-docker.integ.snapshot/tree.json | 46 ++++++++++++++++++- .../aws-ecr-assets/test/image-asset.test.ts | 20 ++++++-- .../test/integ.assets-docker.ts | 8 ++++ .../lib/assets/docker-image-asset.ts | 9 ++++ .../lib/cloud-assembly/metadata-schema.ts | 7 +++ .../schema/assets.schema.json | 4 ++ .../schema/cloud-assembly.schema.json | 4 ++ .../schema/cloud-assembly.version.json | 2 +- packages/@aws-cdk/core/lib/assets.ts | 9 ++++ .../core/lib/stack-synthesizers/legacy.ts | 1 + packages/aws-cdk/lib/assets.ts | 1 + packages/cdk-assets/lib/private/docker.ts | 2 + .../lib/private/handlers/container-images.ts | 1 + .../cdk-assets/test/docker-images.test.ts | 44 ++++++++++++++++++ 22 files changed, 318 insertions(+), 10 deletions(-) create mode 100644 packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/asset.394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38/Dockerfile create mode 100644 packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/asset.394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38/index.py diff --git a/packages/@aws-cdk/aws-ecr-assets/README.md b/packages/@aws-cdk/aws-ecr-assets/README.md index 9be425daadd06..f88cef5b256bb 100644 --- a/packages/@aws-cdk/aws-ecr-assets/README.md +++ b/packages/@aws-cdk/aws-ecr-assets/README.md @@ -92,6 +92,18 @@ const asset = new DockerImageAsset(this, 'MyBuildImage', { }) ``` +You can optionally pass an alternate platform to the `docker build` command by specifying +the `platform` property: + +```ts +import { DockerImageAsset, Platform } from '@aws-cdk/aws-ecr-assets'; + +const asset = new DockerImageAsset(this, 'MyBuildImage', { + directory: path.join(__dirname, 'my-image'), + platform: Platform.LINUX_ARM64, +}) +``` + ## Images from Tarball 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 diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts index 665b67e73105b..b6ddd3f5dafcf 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts @@ -56,6 +56,36 @@ export class NetworkMode { private constructor(public readonly mode: string) {} } +/** + * platform supported by docker + */ +export class Platform { + /** + * Build for linux/amd64 + */ + public static readonly LINUX_AMD64 = new Platform('linux/amd64'); + + /** + * Build for linux/arm64 + */ + public static readonly LINUX_ARM64 = new Platform('linux/arm64'); + + /** + * Used to specify a custom platform + * Use this if the platform name is not yet supported by the CDK. + * + * @param platform The platform to use for docker build + */ + public static custom(platform: string) { + return new Platform(platform); + } + + /** + * @param platform The platform to use for docker build + */ + private constructor(public readonly platform: string) {} +} + /** * Options to control invalidation of `DockerImageAsset` asset hashes */ @@ -101,6 +131,13 @@ export interface DockerImageAssetInvalidationOptions { * @default true */ readonly networkMode?: boolean; + + /** + * Use `platform` while calculating the asset hash + * + * @default true + */ + readonly platform?: boolean; } /** @@ -153,6 +190,13 @@ export interface DockerImageAssetOptions extends FingerprintOptions, FileFingerp */ readonly networkMode?: NetworkMode; + /** + * Platform to build for. _Requires Docker Buildx_. + * + * @default - no platform specified (the current machine architecture will be used) + */ + readonly platform?: Platform; + /** * Options to control which parameters are used to invalidate the asset hash. * @@ -286,6 +330,7 @@ export class DockerImageAsset extends CoreConstruct implements IAsset { if (props.invalidation?.file !== false && props.file) { extraHash.file = props.file; } if (props.invalidation?.repositoryName !== false && props.repositoryName) { extraHash.repositoryName = props.repositoryName; } if (props.invalidation?.networkMode !== false && props.networkMode) { extraHash.networkMode = props.networkMode; } + if (props.invalidation?.platform !== false && props.platform) { extraHash.platform = props.platform; } // add "salt" to the hash in order to invalidate the image in the upgrade to // 1.21.0 which removes the AdoptedRepository resource (and will cause the @@ -318,6 +363,7 @@ export class DockerImageAsset extends CoreConstruct implements IAsset { dockerFile: props.file, sourceHash: staging.assetHash, networkMode: props.networkMode?.mode, + platform: props.platform?.platform, }); this.repository = ecr.Repository.fromRepositoryName(this, 'Repository', location.repositoryName); diff --git a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/asset.394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38/Dockerfile b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/asset.394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38/Dockerfile new file mode 100644 index 0000000000000..235b30e9661ed --- /dev/null +++ b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/asset.394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38/Dockerfile @@ -0,0 +1,5 @@ +FROM public.ecr.aws/lambda/python:3.6 +EXPOSE 8000 +WORKDIR /src +ADD . /src +CMD python3 index.py diff --git a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/asset.394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38/index.py b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/asset.394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38/index.py new file mode 100644 index 0000000000000..2ccedfce3ab76 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/asset.394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38/index.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +import sys +import textwrap +import http.server +import socketserver + +PORT = 8000 + + +class Handler(http.server.SimpleHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header('Content-Type', 'text/html') + self.end_headers() + self.wfile.write(textwrap.dedent('''\ + + It works + +

Hello from the integ test container

+

This container got built and started as part of the integ test.

+ + + ''').encode('utf-8')) + + +def main(): + httpd = http.server.HTTPServer(("", PORT), Handler) + print("serving at port", PORT) + httpd.serve_forever() + + +if __name__ == '__main__': + main() diff --git a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/cdk.out index 90bef2e09ad39..588d7b269d34f 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/integ-assets-docker.template.json b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/integ-assets-docker.template.json index 1f1fffdf5581f..fcdf714d71415 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/integ-assets-docker.template.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/integ-assets-docker.template.json @@ -74,6 +74,48 @@ ] ] } + }, + "ImageUri2": { + "Value": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::AccountId" + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-cdk/assets:0a3355be12051c9984bf2b0b2bba4e6ea535968e5b6e7396449701732fe5ed14" + ] + ] + } + }, + "ImageUri3": { + "Value": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::AccountId" + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-cdk/assets:394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38" + ] + ] + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/integ.json b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/integ.json index d8588aafe50f8..5792f49559a4d 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "20.0.0", "testCases": { - "aws-ecr-assets/test/integ.assets-docker": { + "integ.assets-docker": { "stacks": [ "integ-assets-docker" ], diff --git a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/manifest.json index da54ddfe4c530..d3bf0ba4e8491 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "20.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -26,6 +26,18 @@ "path": "asset.0a3355be12051c9984bf2b0b2bba4e6ea535968e5b6e7396449701732fe5ed14", "sourceHash": "0a3355be12051c9984bf2b0b2bba4e6ea535968e5b6e7396449701732fe5ed14" } + }, + { + "type": "aws:cdk:asset", + "data": { + "repositoryName": "aws-cdk/assets", + "imageTag": "394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38", + "id": "394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38", + "packaging": "container-image", + "path": "asset.394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38", + "sourceHash": "394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38", + "platform": "linux/arm64" + } } ], "/integ-assets-docker/MyUser/Resource": [ @@ -45,6 +57,18 @@ "type": "aws:cdk:logicalId", "data": "ImageUri" } + ], + "/integ-assets-docker/ImageUri2": [ + { + "type": "aws:cdk:logicalId", + "data": "ImageUri2" + } + ], + "/integ-assets-docker/ImageUri3": [ + { + "type": "aws:cdk:logicalId", + "data": "ImageUri3" + } ] }, "displayName": "integ-assets-docker" diff --git a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/tree.json b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/tree.json index 4764014f0323b..e5caed87f3db3 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/assets-docker.integ.snapshot/tree.json @@ -68,6 +68,32 @@ "version": "0.0.0" } }, + "DockerImage3": { + "id": "DockerImage3", + "path": "integ-assets-docker/DockerImage3", + "children": { + "Staging": { + "id": "Staging", + "path": "integ-assets-docker/DockerImage3/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Repository": { + "id": "Repository", + "path": "integ-assets-docker/DockerImage3/Repository", + "constructInfo": { + "fqn": "@aws-cdk/aws-ecr.RepositoryBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecr-assets.DockerImageAsset", + "version": "0.0.0" + } + }, "MyUser": { "id": "MyUser", "path": "integ-assets-docker/MyUser", @@ -99,8 +125,8 @@ { "Action": [ "ecr:BatchCheckLayerAvailability", - "ecr:BatchGetImage", - "ecr:GetDownloadUrlForLayer" + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" ], "Effect": "Allow", "Resource": { @@ -164,6 +190,22 @@ "fqn": "@aws-cdk/core.CfnOutput", "version": "0.0.0" } + }, + "ImageUri2": { + "id": "ImageUri2", + "path": "integ-assets-docker/ImageUri2", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "ImageUri3": { + "id": "ImageUri3", + "path": "integ-assets-docker/ImageUri3", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } } }, "constructInfo": { diff --git a/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts b/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts index 19e5b764407ef..4fe6299ab9c87 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts +++ b/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts @@ -1,12 +1,12 @@ -import * as fs from 'fs'; -import * as path from 'path'; import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import { describeDeprecated, testDeprecated, testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { App, DefaultStackSynthesizer, IgnoreMode, Lazy, LegacyStackSynthesizer, Stack, Stage } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { DockerImageAsset, NetworkMode } from '../lib'; +import * as fs from 'fs'; +import * as path from 'path'; +import { DockerImageAsset, NetworkMode, Platform } from '../lib'; /* eslint-disable quote-props */ @@ -156,6 +156,20 @@ describe('image asset', () => { expect(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).networkMode).toEqual('default'); }); + testFutureBehavior('with platform', flags, App, (app) => { + // GIVEN + const stack = new Stack(app); + // WHEN + new DockerImageAsset(stack, 'Image', { + directory: path.join(__dirname, 'demo-image'), + platform: Platform.LINUX_ARM64, + }); + + // THEN + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + expect(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).platform).toEqual('linux/arm64'); + }); + testFutureBehavior('asset.repository.grantPull can be used to grant a principal permissions to use the image', flags, App, (app) => { // GIVEN const stack = new Stack(app); diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.ts b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.ts index d6c4c2aac75b7..6aecb9ac7bda9 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.ts +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.ts @@ -19,10 +19,18 @@ const asset2 = new assets.DockerImageAsset(stack, 'DockerImage2', { directory: path.join(__dirname, 'demo-image'), }); +const asset3 = new assets.DockerImageAsset(stack, 'DockerImage3', { + directory: path.join(__dirname, 'demo-image'), + platform: assets.Platform.LINUX_ARM64, +}); + const user = new iam.User(stack, 'MyUser'); asset.repository.grantPull(user); asset2.repository.grantPull(user); +asset3.repository.grantPull(user); new cdk.CfnOutput(stack, 'ImageUri', { value: asset.imageUri }); +new cdk.CfnOutput(stack, 'ImageUri2', { value: asset2.imageUri }); +new cdk.CfnOutput(stack, 'ImageUri3', { value: asset3.imageUri }); app.synth(); 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 5b3efeee0b375..ed39ad833b9ce 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 @@ -71,6 +71,15 @@ export interface DockerImageSource { * @default - no networking mode specified */ readonly networkMode?: string; + + /** + * Platform to build for. _Requires Docker Buildx_. + * + * Specify this property to build images on a specific platform/architecture. + * + * @default - current machine platform + */ + readonly platform?: string; } /** 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 b58d02849bd9c..3ed8bfe42cf50 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 @@ -138,6 +138,13 @@ export interface ContainerImageAssetMetadataEntry extends BaseAssetMetadataEntry * @default - no networking mode specified */ readonly networkMode?: string; + + /** + * Platform to build for. _Requires Docker Buildx_. + * + * @default - current machine platform + */ + readonly platform?: string; } /** 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 40134a4e554a5..e2b5aa8780c04 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json @@ -158,6 +158,10 @@ "networkMode": { "description": "Networking mode for the RUN commands during build. _Requires Docker Engine API v1.25+_.\n\nSpecify this property to build images on a specific networking mode. (Default - no networking mode specified)", "type": "string" + }, + "platform": { + "description": "Platform to build for. _Requires Docker Buildx_.\n\nSpecify this property to build images on a specific platform/architecture. (Default - current machine platform)", + "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 19ab465985d24..7f877222e4563 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 @@ -230,6 +230,10 @@ "description": "Networking mode for the RUN commands during build. (Default - no networking mode specified)", "type": "string" }, + "platform": { + "description": "Platform to build for. _Requires Docker Buildx_. (Default - current machine platform)", + "type": "string" + }, "id": { "description": "Logical identifier for the asset", "type": "string" diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json index ccdfc1ff96a9d..588d7b269d34f 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json @@ -1 +1 @@ -{"version":"19.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/assets.ts b/packages/@aws-cdk/core/lib/assets.ts index b9a0ebd2b1bc6..906841f21e8eb 100644 --- a/packages/@aws-cdk/core/lib/assets.ts +++ b/packages/@aws-cdk/core/lib/assets.ts @@ -212,6 +212,15 @@ export interface DockerImageAssetSource { * @default - no networking mode specified */ readonly networkMode?: string; + + /** + * Platform to build for. _Requires Docker Buildx_. + * + * Specify this property to build images on a specific platform. + * + * @default - no platform specified (the current machine architecture will be used) + */ + readonly platform?: string; } /** diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts index 204f3b8ba6827..587181de3ce33 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts @@ -149,6 +149,7 @@ export class LegacyStackSynthesizer extends StackSynthesizer { target: asset.dockerBuildTarget, file: asset.dockerFile, networkMode: asset.networkMode, + platform: asset.platform, }; this.stack.node.addMetadata(cxschema.ArtifactMetadataEntryType.ASSET, metadata); diff --git a/packages/aws-cdk/lib/assets.ts b/packages/aws-cdk/lib/assets.ts index 632292853c670..cb343db5282f1 100644 --- a/packages/aws-cdk/lib/assets.ts +++ b/packages/aws-cdk/lib/assets.ts @@ -123,6 +123,7 @@ async function prepareDockerImageAsset( dockerBuildTarget: asset.target, dockerFile: asset.file, networkMode: asset.networkMode, + platform: asset.platform, }, { repositoryName, imageTag, diff --git a/packages/cdk-assets/lib/private/docker.ts b/packages/cdk-assets/lib/private/docker.ts index 1a9b2293230f6..999d44de7fb4c 100644 --- a/packages/cdk-assets/lib/private/docker.ts +++ b/packages/cdk-assets/lib/private/docker.ts @@ -16,6 +16,7 @@ interface BuildOptions { readonly file?: string; readonly buildArgs?: Record; readonly networkMode?: string; + readonly platform?: string; } export interface DockerCredentialsConfig { @@ -56,6 +57,7 @@ export class Docker { ...options.target ? ['--target', options.target] : [], ...options.file ? ['--file', options.file] : [], ...options.networkMode ? ['--network', options.networkMode] : [], + ...options.platform ? ['--platform', options.platform] : [], '.', ]; await this.execute(buildCommand, { cwd: options.directory }); diff --git a/packages/cdk-assets/lib/private/handlers/container-images.ts b/packages/cdk-assets/lib/private/handlers/container-images.ts index 61ac1004cc714..88b56bf11e00a 100644 --- a/packages/cdk-assets/lib/private/handlers/container-images.ts +++ b/packages/cdk-assets/lib/private/handlers/container-images.ts @@ -134,6 +134,7 @@ class ContainerImageBuilder { target: source.dockerBuildTarget, file: source.dockerFile, networkMode: source.networkMode, + platform: source.platform, }); } diff --git a/packages/cdk-assets/test/docker-images.test.ts b/packages/cdk-assets/test/docker-images.test.ts index 62af7cf399abb..b6fa692dd24a4 100644 --- a/packages/cdk-assets/test/docker-images.test.ts +++ b/packages/cdk-assets/test/docker-images.test.ts @@ -124,6 +124,26 @@ beforeEach(() => { }, }), '/default-network/cdk.out/dockerdir/Dockerfile': 'FROM scratch', + '/platform-arm64/cdk.out/assets.json': JSON.stringify({ + version: Manifest.version(), + dockerImages: { + theAsset: { + source: { + directory: 'dockerdir', + platform: 'linux/arm64', + }, + destinations: { + theDestination: { + region: 'us-north-50', + assumeRoleArn: 'arn:aws:role', + repositoryName: 'repo', + imageTag: 'nopqr', + }, + }, + }, + }, + }), + '/platform-arm64/cdk.out/dockerdir/Dockerfile': 'FROM scratch', }); aws = mockAws(); @@ -239,6 +259,30 @@ describe('with a complete manifest', () => { expectAllSpawns(); expect(true).toBeTruthy(); // Expect no exception, satisfy linter }); + + test('build with platform option', async () => { + pub = new AssetPublishing(AssetManifest.fromPath('/platform-arm64/cdk.out'), { aws }); + const defaultNetworkDockerpath = '/platform-arm64/cdk.out/dockerdir'; + aws.mockEcr.describeImages = mockedApiFailure('ImageNotFoundException', 'File does not exist'); + aws.mockEcr.getAuthorizationToken = mockedApiResult({ + authorizationData: [ + { authorizationToken: 'dXNlcjpwYXNz', proxyEndpoint: 'https://proxy.com/' }, + ], + }); + + const expectAllSpawns = mockSpawn( + { commandLine: ['docker', 'login', '--username', 'user', '--password-stdin', 'https://proxy.com/'] }, + { commandLine: ['docker', 'inspect', 'cdkasset-theasset'], exitCode: 1 }, + { commandLine: ['docker', 'build', '--tag', 'cdkasset-theasset', '--platform', 'linux/arm64', '.'], cwd: defaultNetworkDockerpath }, + { 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', () => {