Skip to content

Commit 74512fa

Browse files
authored
feat(core): Allow passing Docker build secrets (#23778)
Partially closes #14910 and #14395 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Construct Runtime Dependencies: * [ ] This PR adds new construct runtime dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-construct-runtime-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/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*
1 parent be97e4e commit 74512fa

File tree

32 files changed

+349
-44
lines changed

32 files changed

+349
-44
lines changed

packages/@aws-cdk/aws-ecr-assets/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ the `buildArgs` property. It is recommended to skip hashing of `buildArgs` for
5656
values that can change between different machines to maintain a consistent
5757
asset hash.
5858

59+
Additionally, you can supply `buildSecrets`. Your system must have Buildkit
60+
enabled, see https://docs.docker.com/build/buildkit/.
61+
5962
```ts
6063
import { DockerImageAsset } from '@aws-cdk/aws-ecr-assets';
6164

packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ export interface DockerImageAssetInvalidationOptions {
9898
*/
9999
readonly buildArgs?: boolean;
100100

101+
/**
102+
* Use `buildSecrets` while calculating the asset hash
103+
*
104+
* @default true
105+
*/
106+
readonly buildSecrets?: boolean;
107+
101108
/**
102109
* Use `target` while calculating the asset hash
103110
*
@@ -170,6 +177,23 @@ export interface DockerImageAssetOptions extends FingerprintOptions, FileFingerp
170177
*/
171178
readonly buildArgs?: { [key: string]: string };
172179

180+
/**
181+
* Build secrets.
182+
*
183+
* Docker BuildKit must be enabled to use build secrets.
184+
*
185+
* @see https://docs.docker.com/build/buildkit/
186+
*
187+
* @default - no build secrets
188+
*
189+
* @example
190+
*
191+
* {
192+
* 'MY_SECRET': DockerBuildSecret.fromSrc('file.txt')
193+
* }
194+
*/
195+
readonly buildSecrets?: { [key: string]: string }
196+
173197
/**
174198
* Docker target to build to
175199
*
@@ -282,6 +306,11 @@ export class DockerImageAsset extends Construct implements IAsset {
282306
*/
283307
private readonly dockerBuildArgs?: { [key: string]: string };
284308

309+
/**
310+
* Build secrets to pass to the `docker build` command.
311+
*/
312+
private readonly dockerBuildSecrets?: { [key: string]: string };
313+
285314
/**
286315
* Outputs to pass to the `docker build` command.
287316
*/
@@ -345,6 +374,7 @@ export class DockerImageAsset extends Construct implements IAsset {
345374
const extraHash: { [field: string]: any } = {};
346375
if (props.invalidation?.extraHash !== false && props.extraHash) { extraHash.user = props.extraHash; }
347376
if (props.invalidation?.buildArgs !== false && props.buildArgs) { extraHash.buildArgs = props.buildArgs; }
377+
if (props.invalidation?.buildSecrets !== false && props.buildSecrets) { extraHash.buildSecrets = props.buildSecrets; }
348378
if (props.invalidation?.target !== false && props.target) { extraHash.target = props.target; }
349379
if (props.invalidation?.file !== false && props.file) { extraHash.file = props.file; }
350380
if (props.invalidation?.repositoryName !== false && props.repositoryName) { extraHash.repositoryName = props.repositoryName; }
@@ -374,12 +404,14 @@ export class DockerImageAsset extends Construct implements IAsset {
374404
const stack = Stack.of(this);
375405
this.assetPath = staging.relativeStagedPath(stack);
376406
this.dockerBuildArgs = props.buildArgs;
407+
this.dockerBuildSecrets = props.buildSecrets;
377408
this.dockerBuildTarget = props.target;
378409
this.dockerOutputs = props.outputs;
379410

380411
const location = stack.synthesizer.addDockerImageAsset({
381412
directoryName: this.assetPath,
382413
dockerBuildArgs: this.dockerBuildArgs,
414+
dockerBuildSecrets: this.dockerBuildSecrets,
383415
dockerBuildTarget: this.dockerBuildTarget,
384416
dockerFile: props.file,
385417
sourceHash: staging.assetHash,
@@ -420,6 +452,7 @@ export class DockerImageAsset extends Construct implements IAsset {
420452
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PATH_KEY] = this.assetPath;
421453
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY] = this.dockerfilePath;
422454
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_ARGS_KEY] = this.dockerBuildArgs;
455+
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_SECRETS_KEY] = this.dockerBuildSecrets;
423456
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY] = this.dockerBuildTarget;
424457
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY] = resourceProperty;
425458
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_OUTPUTS_KEY] = this.dockerOutputs;
@@ -435,16 +468,25 @@ function validateProps(props: DockerImageAssetProps) {
435468
}
436469

437470
validateBuildArgs(props.buildArgs);
471+
validateBuildSecrets(props.buildSecrets);
438472
}
439473

440-
function validateBuildArgs(buildArgs?: { [key: string]: string }) {
441-
for (const [key, value] of Object.entries(buildArgs || {})) {
474+
function validateBuildProps(buildPropName: string, buildProps?: { [key: string]: string }) {
475+
for (const [key, value] of Object.entries(buildProps || {})) {
442476
if (Token.isUnresolved(key) || Token.isUnresolved(value)) {
443-
throw new Error('Cannot use tokens in keys or values of "buildArgs" since they are needed before deployment');
477+
throw new Error(`Cannot use tokens in keys or values of "${buildPropName}" since they are needed before deployment`);
444478
}
445479
}
446480
}
447481

482+
function validateBuildArgs(buildArgs?: { [key: string]: string }) {
483+
validateBuildProps('buildArgs', buildArgs);
484+
}
485+
486+
function validateBuildSecrets(buildSecrets?: { [key: string]: string }) {
487+
validateBuildProps('buildSecrets', buildSecrets);
488+
}
489+
448490
function toSymlinkFollow(follow?: FollowMode): SymlinkFollowMode | undefined {
449491
switch (follow) {
450492
case undefined: return undefined;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM public.ecr.aws/lambda/python:3.6
2+
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
3+
EXPOSE 8000
4+
WORKDIR /src
5+
ADD . /src
6+
CMD python3 index.py
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/python
2+
import sys
3+
import textwrap
4+
import http.server
5+
import socketserver
6+
7+
PORT = 8000
8+
9+
10+
class Handler(http.server.SimpleHTTPRequestHandler):
11+
def do_GET(self):
12+
self.send_response(200)
13+
self.send_header('Content-Type', 'text/html')
14+
self.end_headers()
15+
self.wfile.write(textwrap.dedent('''\
16+
<!doctype html>
17+
<html><head><title>It works</title></head>
18+
<body>
19+
<h1>Hello from the integ test container</h1>
20+
<p>This container got built and started as part of the integ test.</p>
21+
<img src="https://media.giphy.com/media/nFjDu1LjEADh6/giphy.gif">
22+
</body>
23+
''').encode('utf-8'))
24+
25+
26+
def main():
27+
httpd = http.server.HTTPServer(("", PORT), Handler)
28+
print("serving at port", PORT)
29+
httpd.serve_forever()
30+
31+
32+
if __name__ == '__main__':
33+
main()

packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as fs from 'fs';
22
import * as path from 'path';
33
import { describeDeprecated, testDeprecated } from '@aws-cdk/cdk-build-tools';
44
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
5-
import { App, DefaultStackSynthesizer, IgnoreMode, Lazy, LegacyStackSynthesizer, Stack, Stage } from '@aws-cdk/core';
5+
import { App, DefaultStackSynthesizer, DockerBuildSecret, IgnoreMode, Lazy, LegacyStackSynthesizer, Stack, Stage } from '@aws-cdk/core';
66
import * as cxapi from '@aws-cdk/cx-api';
77
import { DockerImageAsset } from '../lib';
88

@@ -150,6 +150,7 @@ describe('image asset', () => {
150150
const asset5 = new DockerImageAsset(stack, 'Asset5', { directory, file: 'Dockerfile.Custom', target: 'NonDefaultTarget' });
151151
const asset6 = new DockerImageAsset(stack, 'Asset6', { directory, extraHash: 'random-extra' });
152152
const asset7 = new DockerImageAsset(stack, 'Asset7', { directory, outputs: ['123'] });
153+
const asset8 = new DockerImageAsset(stack, 'Asset8', { directory, buildSecrets: { mySecret: DockerBuildSecret.fromSrc('abc.txt') } });
153154

154155
expect(asset1.assetHash).toEqual('13248c55633f3b198a628bb2ea4663cb5226f8b2801051bd0c725950266fd590');
155156
expect(asset2.assetHash).toEqual('36bf205fb9adc5e45ba1c8d534158a0aed96d190eff433af1d90f3b94f96e751');
@@ -158,6 +159,7 @@ describe('image asset', () => {
158159
expect(asset5.assetHash).toEqual('c02bfba13b2e7e1ff5c778a76e10296b9e8d17f7f8252d097f4170ae04ce0eb4');
159160
expect(asset6.assetHash).toEqual('3528d6838647a5e9011b0f35aec514d03ad11af05a94653cdcf4dacdbb070a06');
160161
expect(asset7.assetHash).toEqual('ced0a3076efe217f9cbdff0943e543f36ecf77f70b9a6fe28b8633deb728a462');
162+
expect(asset8.assetHash).toEqual('ffc2718e616141d18c8f4623d13cdfd68cb8f010ca5db31c916c8b5f10c162be');
161163

162164
});
163165

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM public.ecr.aws/lambda/python:3.6
2+
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
3+
EXPOSE 8000
4+
WORKDIR /src
5+
ADD . /src
6+
CMD python3 index.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/python
2+
import sys
3+
import textwrap
4+
import http.server
5+
import socketserver
6+
7+
PORT = 8000
8+
9+
10+
class Handler(http.server.SimpleHTTPRequestHandler):
11+
def do_GET(self):
12+
self.send_response(200)
13+
self.send_header('Content-Type', 'text/html')
14+
self.end_headers()
15+
self.wfile.write(textwrap.dedent('''\
16+
<!doctype html>
17+
<html><head><title>It works</title></head>
18+
<body>
19+
<h1>Hello from the integ test container</h1>
20+
<p>This container got built and started as part of the integ test.</p>
21+
<img src="https://media.giphy.com/media/nFjDu1LjEADh6/giphy.gif">
22+
</body>
23+
''').encode('utf-8'))
24+
25+
26+
def main():
27+
httpd = http.server.HTTPServer(("", PORT), Handler)
28+
print("serving at port", PORT)
29+
httpd.serve_forever()
30+
31+
32+
if __name__ == '__main__':
33+
main()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"version":"24.0.0"}
1+
{"version":"29.0.0"}

packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ-assets-docker.assets.json

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"version": "24.0.0",
2+
"version": "29.0.0",
33
"files": {
4-
"3ef2c8ebbbb128e6fbd2f26a8c80b8154d5fe5157a29846585cb36feac29318e": {
4+
"b1025f887a56783d23c02c714067f4e119f3a3393c9db47c7ce05076e52e58bd": {
55
"source": {
66
"path": "integ-assets-docker.template.json",
77
"packaging": "file"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "3ef2c8ebbbb128e6fbd2f26a8c80b8154d5fe5157a29846585cb36feac29318e.json",
12+
"objectKey": "b1025f887a56783d23c02c714067f4e119f3a3393c9db47c7ce05076e52e58bd.json",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}
@@ -55,6 +55,21 @@
5555
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}"
5656
}
5757
}
58+
},
59+
"60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f": {
60+
"source": {
61+
"directory": "asset.60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f",
62+
"dockerBuildSecrets": {
63+
"mysecret": "src=index.py"
64+
}
65+
},
66+
"destinations": {
67+
"current_account-current_region": {
68+
"repositoryName": "cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}",
69+
"imageTag": "60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f",
70+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}"
71+
}
72+
}
5873
}
5974
}
6075
}

packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ-assets-docker.template.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@
7676
"Value": {
7777
"Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:fa08370824fa0a7eab2c59a4f371fe7631019044d6c906b4268193120dc213b4"
7878
}
79+
},
80+
"ImageUri5": {
81+
"Value": {
82+
"Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f"
83+
}
7984
}
8085
},
8186
"Parameters": {

packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/integ.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "24.0.0",
2+
"version": "29.0.0",
33
"testCases": {
44
"integ.assets-docker": {
55
"stacks": [

packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/manifest.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "24.0.0",
2+
"version": "29.0.0",
33
"artifacts": {
44
"integ-assets-docker.assets": {
55
"type": "cdk:asset-manifest",
@@ -17,7 +17,7 @@
1717
"validateOnSynth": false,
1818
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
1919
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
20-
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3ef2c8ebbbb128e6fbd2f26a8c80b8154d5fe5157a29846585cb36feac29318e.json",
20+
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b1025f887a56783d23c02c714067f4e119f3a3393c9db47c7ce05076e52e58bd.json",
2121
"requiresBootstrapStackVersion": 6,
2222
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
2323
"additionalDependencies": [
@@ -69,6 +69,12 @@
6969
"data": "ImageUri4"
7070
}
7171
],
72+
"/integ-assets-docker/ImageUri5": [
73+
{
74+
"type": "aws:cdk:logicalId",
75+
"data": "ImageUri5"
76+
}
77+
],
7278
"/integ-assets-docker/BootstrapVersion": [
7379
{
7480
"type": "aws:cdk:logicalId",

packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.js.snapshot/tree.json

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,32 @@
112112
"version": "0.0.0"
113113
}
114114
},
115+
"DockerImage5": {
116+
"id": "DockerImage5",
117+
"path": "integ-assets-docker/DockerImage5",
118+
"children": {
119+
"Staging": {
120+
"id": "Staging",
121+
"path": "integ-assets-docker/DockerImage5/Staging",
122+
"constructInfo": {
123+
"fqn": "@aws-cdk/core.AssetStaging",
124+
"version": "0.0.0"
125+
}
126+
},
127+
"Repository": {
128+
"id": "Repository",
129+
"path": "integ-assets-docker/DockerImage5/Repository",
130+
"constructInfo": {
131+
"fqn": "@aws-cdk/aws-ecr.RepositoryBase",
132+
"version": "0.0.0"
133+
}
134+
}
135+
},
136+
"constructInfo": {
137+
"fqn": "@aws-cdk/aws-ecr-assets.DockerImageAsset",
138+
"version": "0.0.0"
139+
}
140+
},
115141
"MyUser": {
116142
"id": "MyUser",
117143
"path": "integ-assets-docker/MyUser",
@@ -236,6 +262,14 @@
236262
"version": "0.0.0"
237263
}
238264
},
265+
"ImageUri5": {
266+
"id": "ImageUri5",
267+
"path": "integ-assets-docker/ImageUri5",
268+
"constructInfo": {
269+
"fqn": "@aws-cdk/core.CfnOutput",
270+
"version": "0.0.0"
271+
}
272+
},
239273
"BootstrapVersion": {
240274
"id": "BootstrapVersion",
241275
"path": "integ-assets-docker/BootstrapVersion",
@@ -263,7 +297,7 @@
263297
"path": "Tree",
264298
"constructInfo": {
265299
"fqn": "constructs.Construct",
266-
"version": "10.1.182"
300+
"version": "10.1.216"
267301
}
268302
}
269303
},

0 commit comments

Comments
 (0)