Skip to content

Commit 61e5495

Browse files
authored
feat(ecr-assets): Support docker outputs flag (#23304)
This adds the `--output` flag as an option when building docker containers. This fixes #20566. ---- ### 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 2318384 commit 61e5495

File tree

24 files changed

+246
-41
lines changed

24 files changed

+246
-41
lines changed

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

+12
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ const asset = new DockerImageAsset(this, 'MyBuildImage', {
106106
})
107107
```
108108

109+
You can optionally pass an array of outputs to the `docker build` command by specifying
110+
the `outputs` property:
111+
112+
```ts
113+
import { DockerImageAsset, Platform } from '@aws-cdk/aws-ecr-assets';
114+
115+
const asset = new DockerImageAsset(this, 'MyBuildImage', {
116+
directory: path.join(__dirname, 'my-image'),
117+
outputs: ['type=local,dest=out'],
118+
})
119+
```
120+
109121
## Images from Tarball
110122

111123
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

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

+27-3
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class NetworkMode {
4747
/**
4848
* @param mode The networking mode to use for docker build
4949
*/
50-
private constructor(public readonly mode: string) {}
50+
private constructor(public readonly mode: string) { }
5151
}
5252

5353
/**
@@ -77,7 +77,7 @@ export class Platform {
7777
/**
7878
* @param platform The platform to use for docker build
7979
*/
80-
private constructor(public readonly platform: string) {}
80+
private constructor(public readonly platform: string) { }
8181
}
8282

8383
/**
@@ -132,6 +132,13 @@ export interface DockerImageAssetInvalidationOptions {
132132
* @default true
133133
*/
134134
readonly platform?: boolean;
135+
136+
/**
137+
* Use `outputs` while calculating the asset hash
138+
*
139+
* @default true
140+
*/
141+
readonly outputs?: boolean;
135142
}
136143

137144
/**
@@ -197,6 +204,14 @@ export interface DockerImageAssetOptions extends FingerprintOptions, FileFingerp
197204
* @default - hash all parameters
198205
*/
199206
readonly invalidation?: DockerImageAssetInvalidationOptions;
207+
208+
/**
209+
* Outputs to pass to the `docker build` command.
210+
*
211+
* @default - no outputs are passed to the build command (default outputs are used)
212+
* @see https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs
213+
*/
214+
readonly outputs?: string[];
200215
}
201216

202217
/**
@@ -267,6 +282,11 @@ export class DockerImageAsset extends Construct implements IAsset {
267282
*/
268283
private readonly dockerBuildArgs?: { [key: string]: string };
269284

285+
/**
286+
* Outputs to pass to the `docker build` command.
287+
*/
288+
private readonly dockerOutputs?: string[];
289+
270290
/**
271291
* Docker target to build to
272292
*/
@@ -330,6 +350,7 @@ export class DockerImageAsset extends Construct implements IAsset {
330350
if (props.invalidation?.repositoryName !== false && props.repositoryName) { extraHash.repositoryName = props.repositoryName; }
331351
if (props.invalidation?.networkMode !== false && props.networkMode) { extraHash.networkMode = props.networkMode; }
332352
if (props.invalidation?.platform !== false && props.platform) { extraHash.platform = props.platform; }
353+
if (props.invalidation?.outputs !== false && props.outputs) { extraHash.outputs = props.outputs; }
333354

334355
// add "salt" to the hash in order to invalidate the image in the upgrade to
335356
// 1.21.0 which removes the AdoptedRepository resource (and will cause the
@@ -354,6 +375,7 @@ export class DockerImageAsset extends Construct implements IAsset {
354375
this.assetPath = staging.relativeStagedPath(stack);
355376
this.dockerBuildArgs = props.buildArgs;
356377
this.dockerBuildTarget = props.target;
378+
this.dockerOutputs = props.outputs;
357379

358380
const location = stack.synthesizer.addDockerImageAsset({
359381
directoryName: this.assetPath,
@@ -363,6 +385,7 @@ export class DockerImageAsset extends Construct implements IAsset {
363385
sourceHash: staging.assetHash,
364386
networkMode: props.networkMode?.mode,
365387
platform: props.platform?.platform,
388+
dockerOutputs: this.dockerOutputs,
366389
});
367390

368391
this.repository = ecr.Repository.fromRepositoryName(this, 'Repository', location.repositoryName);
@@ -393,12 +416,13 @@ export class DockerImageAsset extends Construct implements IAsset {
393416
// tell tools such as SAM CLI that the resourceProperty of this resource
394417
// points to a local path and include the path to de dockerfile, docker build args, and target,
395418
// in order to enable local invocation of this function.
396-
resource.cfnOptions.metadata = resource.cfnOptions.metadata || { };
419+
resource.cfnOptions.metadata = resource.cfnOptions.metadata || {};
397420
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PATH_KEY] = this.assetPath;
398421
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY] = this.dockerfilePath;
399422
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_ARGS_KEY] = this.dockerBuildArgs;
400423
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY] = this.dockerBuildTarget;
401424
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY] = resourceProperty;
425+
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_OUTPUTS_KEY] = this.dockerOutputs;
402426
}
403427

404428
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,15 @@ describe('image asset', () => {
149149
const asset4 = new DockerImageAsset(stack, 'Asset4', { directory, buildArgs: { opt1: '123', opt2: 'boom' } });
150150
const asset5 = new DockerImageAsset(stack, 'Asset5', { directory, file: 'Dockerfile.Custom', target: 'NonDefaultTarget' });
151151
const asset6 = new DockerImageAsset(stack, 'Asset6', { directory, extraHash: 'random-extra' });
152+
const asset7 = new DockerImageAsset(stack, 'Asset7', { directory, outputs: ['123'] });
152153

153154
expect(asset1.assetHash).toEqual('13248c55633f3b198a628bb2ea4663cb5226f8b2801051bd0c725950266fd590');
154155
expect(asset2.assetHash).toEqual('36bf205fb9adc5e45ba1c8d534158a0aed96d190eff433af1d90f3b94f96e751');
155156
expect(asset3.assetHash).toEqual('4c85bd70e73117b7129c2defbe6dc40a8a3872329f4ddca18d75afa671b38276');
156157
expect(asset4.assetHash).toEqual('8a91219a7bb0f58b3282dd84acbf4c03c49c765be54ffb7b125be6a50b6c5645');
157158
expect(asset5.assetHash).toEqual('c02bfba13b2e7e1ff5c778a76e10296b9e8d17f7f8252d097f4170ae04ce0eb4');
158159
expect(asset6.assetHash).toEqual('3528d6838647a5e9011b0f35aec514d03ad11af05a94653cdcf4dacdbb070a06');
160+
expect(asset7.assetHash).toEqual('ced0a3076efe217f9cbdff0943e543f36ecf77f70b9a6fe28b8633deb728a462');
159161

160162
});
161163

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM public.ecr.aws/lambda/python:3.6
2+
EXPOSE 8000
3+
WORKDIR /src
4+
ADD . /src
5+
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()
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"version":"20.0.0"}
1+
{"version":"24.0.0"}

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

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"version": "20.0.0",
2+
"version": "24.0.0",
33
"files": {
4-
"129ed50df2725896720f8593072e2ac18c7c116a97374bea1f973ba8871c68e5": {
4+
"3ef2c8ebbbb128e6fbd2f26a8c80b8154d5fe5157a29846585cb36feac29318e": {
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": "129ed50df2725896720f8593072e2ac18c7c116a97374bea1f973ba8871c68e5.json",
12+
"objectKey": "3ef2c8ebbbb128e6fbd2f26a8c80b8154d5fe5157a29846585cb36feac29318e.json",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}
@@ -40,6 +40,21 @@
4040
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}"
4141
}
4242
}
43+
},
44+
"fa08370824fa0a7eab2c59a4f371fe7631019044d6c906b4268193120dc213b4": {
45+
"source": {
46+
"directory": "asset.fa08370824fa0a7eab2c59a4f371fe7631019044d6c906b4268193120dc213b4",
47+
"dockerOutputs": [
48+
"type=docker"
49+
]
50+
},
51+
"destinations": {
52+
"current_account-current_region": {
53+
"repositoryName": "cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}",
54+
"imageTag": "fa08370824fa0a7eab2c59a4f371fe7631019044d6c906b4268193120dc213b4",
55+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}"
56+
}
57+
}
4358
}
4459
}
4560
}

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

+5
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@
7171
"Value": {
7272
"Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:394b24fcdc153a83b1fc400bf2e812ee67e3a5ffafdf977d531cfe2187d95f38"
7373
}
74+
},
75+
"ImageUri4": {
76+
"Value": {
77+
"Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:fa08370824fa0a7eab2c59a4f371fe7631019044d6c906b4268193120dc213b4"
78+
}
7479
}
7580
},
7681
"Parameters": {

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

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

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

+14-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
{
2-
"version": "20.0.0",
2+
"version": "24.0.0",
33
"artifacts": {
4-
"Tree": {
5-
"type": "cdk:tree",
6-
"properties": {
7-
"file": "tree.json"
8-
}
9-
},
104
"integ-assets-docker.assets": {
115
"type": "cdk:asset-manifest",
126
"properties": {
@@ -23,7 +17,7 @@
2317
"validateOnSynth": false,
2418
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
2519
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
26-
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/129ed50df2725896720f8593072e2ac18c7c116a97374bea1f973ba8871c68e5.json",
20+
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3ef2c8ebbbb128e6fbd2f26a8c80b8154d5fe5157a29846585cb36feac29318e.json",
2721
"requiresBootstrapStackVersion": 6,
2822
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
2923
"additionalDependencies": [
@@ -69,6 +63,12 @@
6963
"data": "ImageUri3"
7064
}
7165
],
66+
"/integ-assets-docker/ImageUri4": [
67+
{
68+
"type": "aws:cdk:logicalId",
69+
"data": "ImageUri4"
70+
}
71+
],
7272
"/integ-assets-docker/BootstrapVersion": [
7373
{
7474
"type": "aws:cdk:logicalId",
@@ -83,6 +83,12 @@
8383
]
8484
},
8585
"displayName": "integ-assets-docker"
86+
},
87+
"Tree": {
88+
"type": "cdk:tree",
89+
"properties": {
90+
"file": "tree.json"
91+
}
8692
}
8793
}
8894
}

0 commit comments

Comments
 (0)