diff --git a/packages/@aws-cdk/aws-ecr-assets/README.md b/packages/@aws-cdk/aws-ecr-assets/README.md index e6f8fa7c5f43a..32059529f1ee1 100644 --- a/packages/@aws-cdk/aws-ecr-assets/README.md +++ b/packages/@aws-cdk/aws-ecr-assets/README.md @@ -56,6 +56,9 @@ the `buildArgs` property. It is recommended to skip hashing of `buildArgs` for values that can change between different machines to maintain a consistent asset hash. +Additionally, you can supply `buildSecrets`. Your system must have Buildkit +enabled, see https://docs.docker.com/build/buildkit/. + ```ts import { DockerImageAsset } from '@aws-cdk/aws-ecr-assets'; 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 a0269f1f495f3..da03e18f80046 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts @@ -98,6 +98,13 @@ export interface DockerImageAssetInvalidationOptions { */ readonly buildArgs?: boolean; + /** + * Use `buildSecrets` while calculating the asset hash + * + * @default true + */ + readonly buildSecrets?: boolean; + /** * Use `target` while calculating the asset hash * @@ -170,6 +177,23 @@ export interface DockerImageAssetOptions extends FingerprintOptions, FileFingerp */ readonly buildArgs?: { [key: string]: string }; + /** + * Build secrets. + * + * Docker BuildKit must be enabled to use build secrets. + * + * @see https://docs.docker.com/build/buildkit/ + * + * @default - no build secrets + * + * @example + * + * { + * 'MY_SECRET': DockerBuildSecret.fromSrc('file.txt') + * } + */ + readonly buildSecrets?: { [key: string]: string } + /** * Docker target to build to * @@ -282,6 +306,11 @@ export class DockerImageAsset extends Construct implements IAsset { */ private readonly dockerBuildArgs?: { [key: string]: string }; + /** + * Build secrets to pass to the `docker build` command. + */ + private readonly dockerBuildSecrets?: { [key: string]: string }; + /** * Outputs to pass to the `docker build` command. */ @@ -345,6 +374,7 @@ export class DockerImageAsset extends Construct implements IAsset { const extraHash: { [field: string]: any } = {}; if (props.invalidation?.extraHash !== false && props.extraHash) { extraHash.user = props.extraHash; } if (props.invalidation?.buildArgs !== false && props.buildArgs) { extraHash.buildArgs = props.buildArgs; } + if (props.invalidation?.buildSecrets !== false && props.buildSecrets) { extraHash.buildSecrets = props.buildSecrets; } if (props.invalidation?.target !== false && props.target) { extraHash.target = props.target; } if (props.invalidation?.file !== false && props.file) { extraHash.file = props.file; } if (props.invalidation?.repositoryName !== false && props.repositoryName) { extraHash.repositoryName = props.repositoryName; } @@ -374,12 +404,14 @@ export class DockerImageAsset extends Construct implements IAsset { const stack = Stack.of(this); this.assetPath = staging.relativeStagedPath(stack); this.dockerBuildArgs = props.buildArgs; + this.dockerBuildSecrets = props.buildSecrets; this.dockerBuildTarget = props.target; this.dockerOutputs = props.outputs; const location = stack.synthesizer.addDockerImageAsset({ directoryName: this.assetPath, dockerBuildArgs: this.dockerBuildArgs, + dockerBuildSecrets: this.dockerBuildSecrets, dockerBuildTarget: this.dockerBuildTarget, dockerFile: props.file, sourceHash: staging.assetHash, @@ -420,6 +452,7 @@ export class DockerImageAsset extends Construct implements IAsset { resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PATH_KEY] = this.assetPath; resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY] = this.dockerfilePath; resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_ARGS_KEY] = this.dockerBuildArgs; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_SECRETS_KEY] = this.dockerBuildSecrets; resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY] = this.dockerBuildTarget; resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY] = resourceProperty; resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_OUTPUTS_KEY] = this.dockerOutputs; @@ -435,16 +468,25 @@ function validateProps(props: DockerImageAssetProps) { } validateBuildArgs(props.buildArgs); + validateBuildSecrets(props.buildSecrets); } -function validateBuildArgs(buildArgs?: { [key: string]: string }) { - for (const [key, value] of Object.entries(buildArgs || {})) { +function validateBuildProps(buildPropName: string, buildProps?: { [key: string]: string }) { + for (const [key, value] of Object.entries(buildProps || {})) { if (Token.isUnresolved(key) || Token.isUnresolved(value)) { - throw new Error('Cannot use tokens in keys or values of "buildArgs" since they are needed before deployment'); + throw new Error(`Cannot use tokens in keys or values of "${buildPropName}" since they are needed before deployment`); } } } +function validateBuildArgs(buildArgs?: { [key: string]: string }) { + validateBuildProps('buildArgs', buildArgs); +} + +function validateBuildSecrets(buildSecrets?: { [key: string]: string }) { + validateBuildProps('buildSecrets', buildSecrets); +} + function toSymlinkFollow(follow?: FollowMode): SymlinkFollowMode | undefined { switch (follow) { case undefined: return undefined; diff --git a/packages/@aws-cdk/aws-ecr-assets/test/demo-image-secret/Dockerfile b/packages/@aws-cdk/aws-ecr-assets/test/demo-image-secret/Dockerfile new file mode 100644 index 0000000000000..72a0396611404 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr-assets/test/demo-image-secret/Dockerfile @@ -0,0 +1,6 @@ +FROM public.ecr.aws/lambda/python:3.6 +RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret +EXPOSE 8000 +WORKDIR /src +ADD . /src +CMD python3 index.py diff --git a/packages/@aws-cdk/aws-ecr-assets/test/demo-image-secret/index.py b/packages/@aws-cdk/aws-ecr-assets/test/demo-image-secret/index.py new file mode 100644 index 0000000000000..2ccedfce3ab76 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr-assets/test/demo-image-secret/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/image-asset.test.ts b/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts index db926dedb562f..2b5c98a100f4d 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 @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { describeDeprecated, testDeprecated } 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 { App, DefaultStackSynthesizer, DockerBuildSecret, IgnoreMode, Lazy, LegacyStackSynthesizer, Stack, Stage } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { DockerImageAsset } from '../lib'; @@ -150,6 +150,7 @@ describe('image asset', () => { const asset5 = new DockerImageAsset(stack, 'Asset5', { directory, file: 'Dockerfile.Custom', target: 'NonDefaultTarget' }); const asset6 = new DockerImageAsset(stack, 'Asset6', { directory, extraHash: 'random-extra' }); const asset7 = new DockerImageAsset(stack, 'Asset7', { directory, outputs: ['123'] }); + const asset8 = new DockerImageAsset(stack, 'Asset8', { directory, buildSecrets: { mySecret: DockerBuildSecret.fromSrc('abc.txt') } }); expect(asset1.assetHash).toEqual('13248c55633f3b198a628bb2ea4663cb5226f8b2801051bd0c725950266fd590'); expect(asset2.assetHash).toEqual('36bf205fb9adc5e45ba1c8d534158a0aed96d190eff433af1d90f3b94f96e751'); @@ -158,6 +159,7 @@ describe('image asset', () => { expect(asset5.assetHash).toEqual('c02bfba13b2e7e1ff5c778a76e10296b9e8d17f7f8252d097f4170ae04ce0eb4'); expect(asset6.assetHash).toEqual('3528d6838647a5e9011b0f35aec514d03ad11af05a94653cdcf4dacdbb070a06'); expect(asset7.assetHash).toEqual('ced0a3076efe217f9cbdff0943e543f36ecf77f70b9a6fe28b8633deb728a462'); + expect(asset8.assetHash).toEqual('ffc2718e616141d18c8f4623d13cdfd68cb8f010ca5db31c916c8b5f10c162be'); }); diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f/Dockerfile b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f/Dockerfile new file mode 100644 index 0000000000000..72a0396611404 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f/Dockerfile @@ -0,0 +1,6 @@ +FROM public.ecr.aws/lambda/python:3.6 +RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret +EXPOSE 8000 +WORKDIR /src +ADD . /src +CMD python3 index.py diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f/index.py b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f/index.py new file mode 100644 index 0000000000000..2ccedfce3ab76 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f/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/integ.assets-docker.js.snapshot/cdk.out b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/cdk.out index e425c25d285ad..d8b441d447f8a 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"24.0.0"} \ No newline at end of file +{"version":"29.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ-assets-docker.assets.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ-assets-docker.assets.json index 4630499e9d3a3..9e78ad767b944 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ-assets-docker.assets.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ-assets-docker.assets.json @@ -1,7 +1,7 @@ { - "version": "24.0.0", + "version": "29.0.0", "files": { - "3ef2c8ebbbb128e6fbd2f26a8c80b8154d5fe5157a29846585cb36feac29318e": { + "b1025f887a56783d23c02c714067f4e119f3a3393c9db47c7ce05076e52e58bd": { "source": { "path": "integ-assets-docker.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "3ef2c8ebbbb128e6fbd2f26a8c80b8154d5fe5157a29846585cb36feac29318e.json", + "objectKey": "b1025f887a56783d23c02c714067f4e119f3a3393c9db47c7ce05076e52e58bd.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } @@ -55,6 +55,21 @@ "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}" } } + }, + "60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f": { + "source": { + "directory": "asset.60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f", + "dockerBuildSecrets": { + "mysecret": "src=index.py" + } + }, + "destinations": { + "current_account-current_region": { + "repositoryName": "cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}", + "imageTag": "60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ-assets-docker.template.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ-assets-docker.template.json index 56e3637207a43..8c7c033450117 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ-assets-docker.template.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ-assets-docker.template.json @@ -76,6 +76,11 @@ "Value": { "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:fa08370824fa0a7eab2c59a4f371fe7631019044d6c906b4268193120dc213b4" } + }, + "ImageUri5": { + "Value": { + "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f" + } } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ.json index 4848cd7f244e2..f4aed5a9c37d0 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "24.0.0", + "version": "29.0.0", "testCases": { "integ.assets-docker": { "stacks": [ diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/manifest.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/manifest.json index 48be2750bd076..cd31c93490241 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "24.0.0", + "version": "29.0.0", "artifacts": { "integ-assets-docker.assets": { "type": "cdk:asset-manifest", @@ -17,7 +17,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3ef2c8ebbbb128e6fbd2f26a8c80b8154d5fe5157a29846585cb36feac29318e.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b1025f887a56783d23c02c714067f4e119f3a3393c9db47c7ce05076e52e58bd.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -69,6 +69,12 @@ "data": "ImageUri4" } ], + "/integ-assets-docker/ImageUri5": [ + { + "type": "aws:cdk:logicalId", + "data": "ImageUri5" + } + ], "/integ-assets-docker/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/tree.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/tree.json index c013950bfa133..32988bdc52723 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/tree.json @@ -112,6 +112,32 @@ "version": "0.0.0" } }, + "DockerImage5": { + "id": "DockerImage5", + "path": "integ-assets-docker/DockerImage5", + "children": { + "Staging": { + "id": "Staging", + "path": "integ-assets-docker/DockerImage5/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Repository": { + "id": "Repository", + "path": "integ-assets-docker/DockerImage5/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", @@ -236,6 +262,14 @@ "version": "0.0.0" } }, + "ImageUri5": { + "id": "ImageUri5", + "path": "integ-assets-docker/ImageUri5", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "integ-assets-docker/BootstrapVersion", @@ -263,7 +297,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.182" + "version": "10.1.216" } } }, 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 1aa9fa392af0a..702b83fe011a5 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 @@ -24,15 +24,24 @@ const asset4 = new assets.DockerImageAsset(stack, 'DockerImage4', { outputs: ['type=docker'], }); +const asset5 = new assets.DockerImageAsset(stack, 'DockerImage5', { + directory: path.join(__dirname, 'demo-image-secret'), + buildSecrets: { + mysecret: cdk.DockerBuildSecret.fromSrc('index.py'), + }, +}); + const user = new iam.User(stack, 'MyUser'); asset.repository.grantPull(user); asset2.repository.grantPull(user); asset3.repository.grantPull(user); asset4.repository.grantPull(user); +asset5.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 }); new cdk.CfnOutput(stack, 'ImageUri4', { value: asset4.imageUri }); +new cdk.CfnOutput(stack, 'ImageUri5', { value: asset5.imageUri }); app.synth(); diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/cdk.out b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/cdk.out index 588d7b269d34f..d8b441d447f8a 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"29.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/integ.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/integ.json index c1dff06736e53..7c8541b18231b 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/integ.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "29.0.0", "testCases": { "integ.nested-stacks-docker": { "stacks": [ diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/manifest.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/manifest.json index f318c19a2b7cf..a8cd150aeb96f 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/manifest.json @@ -1,12 +1,6 @@ { - "version": "20.0.0", + "version": "29.0.0", "artifacts": { - "Tree": { - "type": "cdk:tree", - "properties": { - "file": "tree.json" - } - }, "nested-stacks-docker.assets": { "type": "cdk:asset-manifest", "properties": { @@ -77,6 +71,12 @@ ] }, "displayName": "nested-stacks-docker" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/nested-stacks-docker.assets.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/nested-stacks-docker.assets.json index 8f3fdeddfade8..aa40b93d5962f 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/nested-stacks-docker.assets.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/nested-stacks-docker.assets.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "29.0.0", "files": { "eaf17d410c9c3958b50b406011121bab5f3147b4a61a0f93a9ab1db097033867": { "source": { diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/tree.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/tree.json index 9bbc4b8027746..0360789f62220 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.js.snapshot/tree.json @@ -4,14 +4,6 @@ "id": "App", "path": "", "children": { - "Tree": { - "id": "Tree", - "path": "Tree", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" - } - }, "nested-stacks-docker": { "id": "nested-stacks-docker", "path": "nested-stacks-docker", @@ -28,8 +20,8 @@ "id": "Staging", "path": "nested-stacks-docker/nested-stack-with-image/my-image/Staging", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "Repository": { @@ -142,14 +134,14 @@ "id": "output", "path": "nested-stacks-docker/nested-stack-with-image/output", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.NestedStack", + "version": "0.0.0" } }, "nested-stack-with-image.NestedStack": { @@ -185,26 +177,50 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnStack", + "version": "0.0.0" } } }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.216" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "nested-stacks-docker/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "nested-stacks-docker/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" } } }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.216" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file 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 5fa99faf9a687..809ef1c9f91f4 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 @@ -63,6 +63,15 @@ export interface DockerImageSource { */ readonly dockerBuildArgs?: { [name: string]: string }; + /** + * Additional build secrets + * + * Only allowed when `directory` is set. + * + * @default - No additional build secrets + */ + readonly dockerBuildSecrets?: { [name: string]: string }; + /** * Networking mode for the RUN commands during build. _Requires Docker Engine API v1.25+_. * 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 93f6f6a85e2f9..c3d4ac127a46f 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 @@ -118,6 +118,13 @@ export interface ContainerImageAssetMetadataEntry extends BaseAssetMetadataEntry */ readonly buildArgs?: { [key: string]: string }; + /** + * Build secrets to pass to the `docker build` command + * + * @default no build secrets are passed + */ + readonly buildSecrets?: { [key: string]: string }; + /** * Docker target to build to * 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 c861e5f3819db..28dea7efbf357 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json @@ -155,6 +155,13 @@ "type": "string" } }, + "dockerBuildSecrets": { + "description": "Additional build secrets\n\nOnly allowed when `directory` is set. (Default - No additional build secrets)", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "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" 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 ae253314c97c0..ec3245e51505d 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 @@ -218,6 +218,13 @@ "type": "string" } }, + "buildSecrets": { + "description": "Build secrets to pass to the `docker build` command (Default no build secrets are passed)", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "target": { "description": "Docker target to build to (Default no build target)", "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 d8b441d447f8a..ae4b03c54e770 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":"29.0.0"} \ No newline at end of file +{"version":"30.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 ea7903f7f23c2..10e1a61e7e5d0 100644 --- a/packages/@aws-cdk/core/lib/assets.ts +++ b/packages/@aws-cdk/core/lib/assets.ts @@ -174,6 +174,19 @@ export interface DockerImageAssetSource { */ readonly dockerBuildArgs?: { [key: string]: string }; + /** + * Build secrets to pass to the `docker build` command. + * + * Since Docker build secrets are resolved before deployment, keys and + * values cannot refer to unresolved tokens (such as `lambda.functionArn` or + * `queue.queueUrl`). + * + * Only allowed when `directoryName` is specified. + * + * @default - no build secrets are passed + */ + readonly dockerBuildSecrets?: { [key: string]: string }; + /** * Docker target to build to * diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index d88a84043a652..5d97d65d967fa 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -5,6 +5,24 @@ import { FileSystem } from './fs'; import { dockerExec } from './private/asset-staging'; import { quiet, reset } from './private/jsii-deprecated'; +/** + * Methods to build Docker CLI arguments for builds using secrets. + * + * Docker BuildKit must be enabled to use build secrets. + * + * @see https://docs.docker.com/build/buildkit/ + */ +export class DockerBuildSecret { + /** + * A Docker build secret from a file source + * @param src The path to the source file, relative to the build directory. + * @returns The latter half required for `--secret` + */ + public static fromSrc(src: string): string { + return `src=${src}`; + } +} + /** * Bundling options * diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/asset-manifest-builder.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/asset-manifest-builder.ts index 92497aacb27fc..801a951630192 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/asset-manifest-builder.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/asset-manifest-builder.ts @@ -65,6 +65,7 @@ export class AssetManifestBuilder { executable: asset.executable, directory: asset.directoryName, dockerBuildArgs: asset.dockerBuildArgs, + dockerBuildSecrets: asset.dockerBuildSecrets, dockerBuildTarget: asset.dockerBuildTarget, dockerFile: asset.dockerFile, networkMode: asset.networkMode, diff --git a/packages/@aws-cdk/core/test/bundling.test.ts b/packages/@aws-cdk/core/test/bundling.test.ts index ed15b8daa4282..fe2e0cea608f7 100644 --- a/packages/@aws-cdk/core/test/bundling.test.ts +++ b/packages/@aws-cdk/core/test/bundling.test.ts @@ -2,7 +2,7 @@ import * as child_process from 'child_process'; import * as crypto from 'crypto'; import * as path from 'path'; import * as sinon from 'sinon'; -import { DockerImage, FileSystem } from '../lib'; +import { DockerBuildSecret, DockerImage, FileSystem } from '../lib'; const dockerCmd = process.env.CDK_DOCKER ?? 'docker'; @@ -600,4 +600,12 @@ describe('bundling', () => { 'cool', 'command', ], { stdio: ['ignore', process.stderr, 'inherit'] })).toEqual(true); }); + + test('ensure correct Docker CLI arguments are returned', () => { + // GIVEN + const fromSrc = DockerBuildSecret.fromSrc('path.json'); + + // THEN + expect(fromSrc).toEqual('src=path.json'); + }); }); diff --git a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts index 2be3a7e008cd7..7d8ffbc2635ea 100644 --- a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts +++ b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts @@ -223,6 +223,27 @@ describe('new style synthesis', () => { }); + test('dockerBuildArgs or dockerBuildSecrets without directoryName', () => { + // WHEN + expect(() => { + stack.synthesizer.addDockerImageAsset({ + sourceHash: 'abcdef', + dockerBuildArgs: { + ABC: '123', + }, + }); + }).toThrowError(/Exactly one of 'directoryName' or 'executable' is required/); + + expect(() => { + stack.synthesizer.addDockerImageAsset({ + sourceHash: 'abcdef', + dockerBuildSecrets: { + DEF: '456', + }, + }); + }).toThrowError(/Exactly one of 'directoryName' or 'executable' is required/); + }); + test('synthesis', () => { // GIVEN stack.synthesizer.addFileAsset({ diff --git a/packages/@aws-cdk/cx-api/lib/assets.ts b/packages/@aws-cdk/cx-api/lib/assets.ts index ea6585c2a1103..3cc8312f646d4 100644 --- a/packages/@aws-cdk/cx-api/lib/assets.ts +++ b/packages/@aws-cdk/cx-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_SECRETS_KEY = 'aws:asset:docker-build-secrets'; export const ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY = 'aws:asset:docker-build-target'; export const ASSET_RESOURCE_METADATA_PROPERTY_KEY = 'aws:asset:property'; export const ASSET_RESOURCE_METADATA_IS_BUNDLED_KEY = 'aws:asset:is-bundled'; diff --git a/packages/cdk-assets/lib/private/docker.ts b/packages/cdk-assets/lib/private/docker.ts index 4a812aba64a26..7cca00a2d0cbd 100644 --- a/packages/cdk-assets/lib/private/docker.ts +++ b/packages/cdk-assets/lib/private/docker.ts @@ -15,6 +15,7 @@ interface BuildOptions { readonly target?: string; readonly file?: string; readonly buildArgs?: Record; + readonly buildSecrets?: Record; readonly networkMode?: string; readonly platform?: string; readonly outputs?: string[]; @@ -54,6 +55,7 @@ export class Docker { const buildCommand = [ 'build', ...flatten(Object.entries(options.buildArgs || {}).map(([k, v]) => ['--build-arg', `${k}=${v}`])), + ...flatten(Object.entries(options.buildSecrets || {}).map(([k, v]) => ['--secret', `id=${k},${v}`])), '--tag', options.tag, ...options.target ? ['--target', options.target] : [], ...options.file ? ['--file', options.file] : [], diff --git a/packages/cdk-assets/lib/private/handlers/container-images.ts b/packages/cdk-assets/lib/private/handlers/container-images.ts index 750247f4bbab0..811d02e1a8d01 100644 --- a/packages/cdk-assets/lib/private/handlers/container-images.ts +++ b/packages/cdk-assets/lib/private/handlers/container-images.ts @@ -167,6 +167,7 @@ class ContainerImageBuilder { directory: fullPath, tag: localTagName, buildArgs: source.dockerBuildArgs, + buildSecrets: source.dockerBuildSecrets, target: source.dockerBuildTarget, file: source.dockerFile, networkMode: source.networkMode,