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 2c8c71879e113..df705ec4b53f8 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 * @@ -163,6 +170,15 @@ export interface DockerImageAssetOptions extends FingerprintOptions, FileFingerp */ readonly buildArgs?: { [key: string]: string }; + /** + * Build secrets to pass to the `docker build` command. + * + * Docker BuildKit must be enabled. + * + * @default - no build secrets are passed + */ + readonly buildSecrets?: { [key: string]: string }; + /** * Docker target to build to * @@ -267,6 +283,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 }; + /** * Docker target to build to */ @@ -325,6 +346,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; } @@ -353,11 +375,13 @@ 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; const location = stack.synthesizer.addDockerImageAsset({ directoryName: this.assetPath, dockerBuildArgs: this.dockerBuildArgs, + dockerBuildSecrets: this.dockerBuildSecrets, dockerBuildTarget: this.dockerBuildTarget, dockerFile: props.file, sourceHash: staging.assetHash, @@ -411,6 +435,7 @@ function validateProps(props: DockerImageAssetProps) { } validateBuildArgs(props.buildArgs); + validateBuildSecrets(props.buildSecrets); } function validateBuildArgs(buildArgs?: { [key: string]: string }) { @@ -421,6 +446,14 @@ function validateBuildArgs(buildArgs?: { [key: string]: string }) { } } +function validateBuildSecrets(buildSecrets?: { [key: string]: string }) { + for (const [key, value] of Object.entries(buildSecrets || {})) { + if (Token.isUnresolved(key) || Token.isUnresolved(value)) { + throw new Error('Cannot use tokens in keys or values of "buildSecrets" since they are needed before deployment'); + } + } +} + 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/demo-image-secret/secret.txt b/packages/@aws-cdk/aws-ecr-assets/test/demo-image-secret/secret.txt new file mode 100644 index 0000000000000..c301d47a65f0e --- /dev/null +++ b/packages/@aws-cdk/aws-ecr-assets/test/demo-image-secret/secret.txt @@ -0,0 +1 @@ +test-secret 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 775345b10d3e8..59766f6ae0c43 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 @@ -125,6 +125,24 @@ describe('image asset', () => { })).toThrow(expected); }); + test('fails if using tokens in build secrets keys or values', () => { + // GIVEN + const stack = new Stack(); + const token = Lazy.string({ produce: () => 'foo' }); + const expected = /Cannot use tokens in keys or values of "buildSecrets" since they are needed before deployment/; + + // THEN + expect(() => new DockerImageAsset(stack, 'MyAsset1', { + directory: path.join(__dirname, 'demo-image'), + buildSecrets: { [token]: 'value' }, + })).toThrow(expected); + + expect(() => new DockerImageAsset(stack, 'MyAsset2', { + directory: path.join(__dirname, 'demo-image'), + buildSecrets: { key: token }, + })).toThrow(expected); + }); + testDeprecated('fails if using token as repositoryName', () => { // GIVEN const stack = new Stack(); @@ -149,6 +167,7 @@ describe('image asset', () => { const asset4 = new DockerImageAsset(stack, 'Asset4', { directory, buildArgs: { opt1: '123', opt2: 'boom' } }); 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, buildSecrets: { a: 'b' } }); expect(asset1.assetHash).toEqual('13248c55633f3b198a628bb2ea4663cb5226f8b2801051bd0c725950266fd590'); expect(asset2.assetHash).toEqual('36bf205fb9adc5e45ba1c8d534158a0aed96d190eff433af1d90f3b94f96e751'); @@ -156,6 +175,7 @@ describe('image asset', () => { expect(asset4.assetHash).toEqual('8a91219a7bb0f58b3282dd84acbf4c03c49c765be54ffb7b125be6a50b6c5645'); expect(asset5.assetHash).toEqual('c02bfba13b2e7e1ff5c778a76e10296b9e8d17f7f8252d097f4170ae04ce0eb4'); expect(asset6.assetHash).toEqual('3528d6838647a5e9011b0f35aec514d03ad11af05a94653cdcf4dacdbb070a06'); + expect(asset7.assetHash).toEqual('adef40a1e694f03f8b45807d6061af13fa75d08442d4370a6619afb09d0ef254'); }); diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9/Dockerfile b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9/Dockerfile new file mode 100644 index 0000000000000..72a0396611404 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9/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.600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9/index.py b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9/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.600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9/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/asset.600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9/secret.txt b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9/secret.txt new file mode 100644 index 0000000000000..c301d47a65f0e --- /dev/null +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/asset.600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9/secret.txt @@ -0,0 +1 @@ +test-secret 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 588d7b269d34f..5557f5ddbf425 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":"20.0.0"} \ No newline at end of file +{"version":"23.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 f313e184ea125..2adc49ad30bd9 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": "20.0.0", + "version": "23.0.0", "files": { - "129ed50df2725896720f8593072e2ac18c7c116a97374bea1f973ba8871c68e5": { + "8fec9a4f90e122913ce423ddc6ec9683e8785b8fec789096161d8a0f4c23f3d5": { "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": "129ed50df2725896720f8593072e2ac18c7c116a97374bea1f973ba8871c68e5.json", + "objectKey": "8fec9a4f90e122913ce423ddc6ec9683e8785b8fec789096161d8a0f4c23f3d5.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } @@ -40,6 +40,21 @@ "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}" } } + }, + "600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9": { + "source": { + "directory": "asset.600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9", + "dockerBuildSecrets": { + "mysecret": "/Users/danielwiltshire/repos/aws-cdk/packages/@aws-cdk/aws-ecr-assets/test/demo-image-secret/secret.txt" + } + }, + "destinations": { + "current_account-current_region": { + "repositoryName": "cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}", + "imageTag": "600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9", + "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 6824c36b9e856..002774722c445 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 @@ -71,6 +71,11 @@ "Value": { "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38" } + }, + "ImageUri4": { + "Value": { + "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:600ab433dc112602ca7ba940e2720696dd6bfdb7abe79a608c701e522d0afcd9" + } } }, "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 5792f49559a4d..10891cae8c743 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": "20.0.0", + "version": "23.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 31cbfe3fe4d55..550f30a348911 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,12 +1,6 @@ { - "version": "20.0.0", + "version": "23.0.0", "artifacts": { - "Tree": { - "type": "cdk:tree", - "properties": { - "file": "tree.json" - } - }, "integ-assets-docker.assets": { "type": "cdk:asset-manifest", "properties": { @@ -23,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}/129ed50df2725896720f8593072e2ac18c7c116a97374bea1f973ba8871c68e5.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/8fec9a4f90e122913ce423ddc6ec9683e8785b8fec789096161d8a0f4c23f3d5.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -69,6 +63,12 @@ "data": "ImageUri3" } ], + "/integ-assets-docker/ImageUri4": [ + { + "type": "aws:cdk:logicalId", + "data": "ImageUri4" + } + ], "/integ-assets-docker/BootstrapVersion": [ { "type": "aws:cdk:logicalId", @@ -83,6 +83,12 @@ ] }, "displayName": "integ-assets-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.assets-docker.js.snapshot/tree.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/tree.json index 4ac3cf9f4c4db..8439e7a6ef8f1 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 @@ -4,14 +4,6 @@ "id": "App", "path": "", "children": { - "Tree": { - "id": "Tree", - "path": "Tree", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" - } - }, "integ-assets-docker": { "id": "integ-assets-docker", "path": "integ-assets-docker", @@ -24,8 +16,8 @@ "id": "Staging", "path": "integ-assets-docker/DockerImage/Staging", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "Repository": { @@ -50,8 +42,8 @@ "id": "Staging", "path": "integ-assets-docker/DockerImage2/Staging", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "Repository": { @@ -76,8 +68,8 @@ "id": "Staging", "path": "integ-assets-docker/DockerImage3/Staging", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "Repository": { @@ -94,6 +86,32 @@ "version": "0.0.0" } }, + "DockerImage4": { + "id": "DockerImage4", + "path": "integ-assets-docker/DockerImage4", + "children": { + "Staging": { + "id": "Staging", + "path": "integ-assets-docker/DockerImage4/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Repository": { + "id": "Repository", + "path": "integ-assets-docker/DockerImage4/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", @@ -190,36 +208,68 @@ "id": "ImageUri", "path": "integ-assets-docker/ImageUri", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" } }, "ImageUri2": { "id": "ImageUri2", "path": "integ-assets-docker/ImageUri2", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" } }, "ImageUri3": { "id": "ImageUri3", "path": "integ-assets-docker/ImageUri3", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "ImageUri4": { + "id": "ImageUri4", + "path": "integ-assets-docker/ImageUri4", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-assets-docker/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-assets-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.189" } } }, "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/aws-ecr-assets/test/integ.assets-docker.ts b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.ts index f71b41e7b09a3..b0bdb741d1a46 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,13 +19,23 @@ const asset3 = new assets.DockerImageAsset(stack, 'DockerImage3', { platform: assets.Platform.LINUX_ARM64, }); + +const asset4 = new assets.DockerImageAsset(stack, 'DockerImage4', { + directory: path.join(__dirname, 'demo-image-secret'), + buildSecrets: { + mysecret: path.join(__dirname, 'demo-image-secret', 'secret.txt'), + }, +}); + const user = new iam.User(stack, 'MyUser'); asset.repository.grantPull(user); asset2.repository.grantPull(user); asset3.repository.grantPull(user); +asset4.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 }); 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 ed39ad833b9ce..bdb2b82c4aecf 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 + * + * Requires BuildKit to be enabled. + * + * @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 3ed8bfe42cf50..163442b9d0884 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,15 @@ export interface ContainerImageAssetMetadataEntry extends BaseAssetMetadataEntry */ readonly buildArgs?: { [key: string]: string }; + /** + * Build secrets to pass to the `docker build` command + * + * Docker BuildKit must be enabled. + * + * @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 e2b5aa8780c04..d371b4cf2733f 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\nRequires BuildKit to be enabled. (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 194ea1ac1f615..1de6eae86c1e9 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\n\nDocker BuildKit must be enabled. (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 145739f539580..5557f5ddbf425 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":"22.0.0"} \ No newline at end of file +{"version":"23.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 8c37d8e9ed2c2..e2177056a8941 100644 --- a/packages/@aws-cdk/core/lib/assets.ts +++ b/packages/@aws-cdk/core/lib/assets.ts @@ -174,6 +174,15 @@ export interface DockerImageAssetSource { */ readonly dockerBuildArgs?: { [key: string]: string }; + /** + * Build secrets to pass to the `docker build` command. + * + * Docker BuildKit must be enabled + * + * @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 a78f5e8a6aca0..afa090f7d7052 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -277,6 +277,7 @@ export class DockerImage extends BundlingDockerImage { */ public static fromBuild(path: string, options: DockerBuildOptions = {}) { const buildArgs = options.buildArgs || {}; + const buildSecrets = options.buildSecrets || {}; if (options.file && isAbsolute(options.file)) { throw new Error(`"file" must be relative to the docker build directory. Got ${options.file}`); @@ -293,6 +294,7 @@ export class DockerImage extends BundlingDockerImage { ...(options.platform ? ['--platform', options.platform] : []), ...(options.targetStage ? ['--target', options.targetStage] : []), ...flatten(Object.entries(buildArgs).map(([k, v]) => ['--build-arg', `${k}=${v}`])), + ...flatten(Object.entries(buildSecrets).map(([k, v]) => ['--secret', `id=${k},src=${v}`])), path, ]; @@ -506,6 +508,13 @@ export interface DockerBuildOptions { */ readonly buildArgs?: { [key: string]: string }; + /** + * Build secrets + * + * @default - no build secrets + */ + readonly buildSecrets?: { [key: string]: string }; + /** * Name of the Dockerfile, must relative to the docker build path. * 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 73e23c330c9b1..e98e43a7a5b0a 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, @@ -241,6 +242,7 @@ function validateDockerImageAssetSource(asset: DockerImageAssetSource) { check('dockerBuildArgs'); check('dockerBuildTarget'); + check('dockerBuildSecrets'); check('dockerFile'); function check(key: K) { diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts index c7c76ddc94819..767d16bb2c1b0 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts @@ -130,6 +130,7 @@ export class LegacyStackSynthesizer extends StackSynthesizer { path: asset.directoryName, sourceHash: asset.sourceHash, buildArgs: asset.dockerBuildArgs, + buildSecrets: asset.dockerBuildSecrets, target: asset.dockerBuildTarget, file: 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 3879d0b27fc41..cd26787f21236 100644 --- a/packages/@aws-cdk/core/test/bundling.test.ts +++ b/packages/@aws-cdk/core/test/bundling.test.ts @@ -63,6 +63,9 @@ describe('bundling', () => { buildArgs: { TEST_ARG: 'cdk-test', }, + buildSecrets: { + a: 'b', + }, }); image.run(); @@ -71,12 +74,16 @@ describe('bundling', () => { buildArgs: { TEST_ARG: 'cdk-test', }, + buildSecrets: { + a: 'b', + }, })).digest('hex'); const tag = `cdk-${tagHash}`; expect(spawnSyncStub.firstCall.calledWith('docker', [ 'build', '-t', tag, '--build-arg', 'TEST_ARG=cdk-test', + '--secret', 'id=a,src=b', 'docker-path', ])).toEqual(true); diff --git a/packages/aws-cdk/lib/assets.ts b/packages/aws-cdk/lib/assets.ts index 6f96e6af2c6aa..bdeb67a94e093 100644 --- a/packages/aws-cdk/lib/assets.ts +++ b/packages/aws-cdk/lib/assets.ts @@ -120,6 +120,7 @@ async function prepareDockerImageAsset( assetManifest.addDockerImageAsset(asset.sourceHash, { directory: asset.path, dockerBuildArgs: asset.buildArgs, + dockerBuildSecrets: asset.buildSecrets, dockerBuildTarget: asset.target, dockerFile: asset.file, networkMode: asset.networkMode, diff --git a/packages/cdk-assets/lib/private/docker.ts b/packages/cdk-assets/lib/private/docker.ts index 6c0a302c19eb2..a771e3a3274b4 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; } @@ -53,6 +54,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},src=${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 1d9e6ac46f53c..558e19a4284ed 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,