Skip to content

Commit a7b39f5

Browse files
authored
feat(assets): support networking mode for DockerImageAsset (#18114)
As we are not allowed to specify networking mode for DockerImageAsset, users deploying cdk on containerized environment like Kubernetes will not be able to bundle assets without the build option `--network host`. With this support, we are allowed to: * [x] bundle image assets on a specific networking mode with the new `networkMode` property of `DockerImageAsset` from `aws-ecr-assets`. * [x] bundle DockerImageFunction from aws-lambda on a specific networking mode. * [x] bundle container images for AWS Fargate from on a specific networking mode. Close #15516. --- ## The possible values of `--network` According to Docker CLI, the default value for `--network` will be `default` if omitted. ```shell $ docker build --help Usage: docker build [OPTIONS] PATH | URL | - Build an image from a Dockerfile Options: ... --network string Set the networking mode for the RUN instructions during build (default "default") ... ``` According to the [Docker Official Docs- API 1.25](https://docs.docker.com/engine/api/v1.25/#operation/ImageBuild): > supported standard values are: `bridge`, `host`, `none`, and `container:<name|id>`. Any other value is taken as a custom network's name to which this container should connect to. But according to [Source Code - docker/engine BuildKit](https://github.com/docker/engine/blob/8955d8da8951695a98eb7e15bead19d402c6eb27/builder/builder-next/builder.go#L308-L314), the value `bridge` is not accepted by BuildKit & should use the value `default` instead. Therefore, the static values for `NetworkMode` are `default`, `host` & `none`, with 2 static functions `NetworkMode.fromContainer()` to construct a `container:<name|id>` & `NetworkMode.custom()` to construct a custom networking mode. ``` $ DOCKER_BUILDKIT=1 docker build --network=bridge . Error response from daemon: network mode "bridge" not supported by buildkit ``` References: * [Docker Official Docs- API 1.25](https://docs.docker.com/engine/api/v1.25/#operation/ImageBuild) * [Docker Official Docs - Use the default bridge network](https://docs.docker.com/network/bridge/#use-the-default-bridge-network) * [Source Code - docker/engine BuildKit](https://github.com/docker/engine/blob/8955d8da8951695a98eb7e15bead19d402c6eb27/builder/builder-next/builder.go#L308-L314) --- ## Builder experience with `aws-ecr-assets` Specify `networkMode` with `DEFAULT` or `HOST` for docker image assets ```ts new assets.DockerImageAsset(stack, 'DockerImage', { directory: path.join(__dirname, 'demo-image'), networkMode: NetworkMode.HOST, }); ``` ## Builder experience with `aws-ecs` Specify `networkMode` with `DEFAULT` or `HOST` for container image ```ts taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromAsset(path.join(__dirname, '../demo-image'), { networkMode: NetworkMode.DEFAULT, }), portMappings: [{ containerPort: 8000, }], }); ``` ## Builder experience with `aws-lambda` Specify `networkMode` with `DEFAULT` or `HOST` from docker image assets ```ts new DockerImageFunction(this, 'MyLambda', { code: DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-arm64-handler'), { networkMode: NetworkMode.DEFAULT, }), }); ```
1 parent 460836a commit a7b39f5

File tree

14 files changed

+172
-3
lines changed

14 files changed

+172
-3
lines changed

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

+12
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,18 @@ const asset = new DockerImageAsset(this, 'MyBuildImage', {
8080
});
8181
```
8282

83+
You can optionally pass networking mode to the `docker build` command by specifying
84+
the `networkMode` property:
85+
86+
```ts
87+
import { DockerImageAsset, NetworkMode } from '@aws-cdk/aws-ecr-assets';
88+
89+
const asset = new DockerImageAsset(this, 'MyBuildImage', {
90+
directory: path.join(__dirname, 'my-image'),
91+
networkMode: NetworkMode.HOST,
92+
})
93+
```
94+
8395
## Images from Tarball
8496

8597
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

+60
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,50 @@ import { FingerprintOptions, FollowMode, IAsset } from '@aws-cdk/assets';
1212
// eslint-disable-next-line no-duplicate-imports, import/order
1313
import { Construct as CoreConstruct } from '@aws-cdk/core';
1414

15+
/**
16+
* networking mode on build time supported by docker
17+
*/
18+
export class NetworkMode {
19+
/**
20+
* The default networking mode if omitted, create a network stack on the default Docker bridge
21+
*/
22+
public static readonly DEFAULT = new NetworkMode('default');
23+
24+
/**
25+
* Use the Docker host network stack
26+
*/
27+
public static readonly HOST = new NetworkMode('host');
28+
29+
/**
30+
* Disable the network stack, only the loopback device will be created
31+
*/
32+
public static readonly NONE = new NetworkMode('none');
33+
34+
/**
35+
* Reuse another container's network stack
36+
*
37+
* @param containerId The target container's id or name
38+
*/
39+
public static fromContainer(containerId: string) {
40+
return new NetworkMode(`container:${containerId}`);
41+
}
42+
43+
/**
44+
* Used to specify a custom networking mode
45+
* Use this if the networking mode name is not yet supported by the CDK.
46+
*
47+
* @param mode The networking mode to use for docker build
48+
*/
49+
public static custom(mode: string) {
50+
return new NetworkMode(mode);
51+
}
52+
53+
/**
54+
* @param mode The networking mode to use for docker build
55+
*/
56+
private constructor(public readonly mode: string) {}
57+
}
58+
1559
/**
1660
* Options to control invalidation of `DockerImageAsset` asset hashes
1761
*/
@@ -50,6 +94,13 @@ export interface DockerImageAssetInvalidationOptions {
5094
* @default true
5195
*/
5296
readonly repositoryName?: boolean;
97+
98+
/**
99+
* Use `networkMode` while calculating the asset hash
100+
*
101+
* @default true
102+
*/
103+
readonly networkMode?: boolean;
53104
}
54105

55106
/**
@@ -95,6 +146,13 @@ export interface DockerImageAssetOptions extends FingerprintOptions, FileFingerp
95146
*/
96147
readonly file?: string;
97148

149+
/**
150+
* Networking mode for the RUN commands during build. Support docker API 1.25+.
151+
*
152+
* @default - no networking mode specified (the default networking mode `NetworkMode.DEFAULT` will be used)
153+
*/
154+
readonly networkMode?: NetworkMode;
155+
98156
/**
99157
* Options to control which parameters are used to invalidate the asset hash.
100158
*
@@ -227,6 +285,7 @@ export class DockerImageAsset extends CoreConstruct implements IAsset {
227285
if (props.invalidation?.target !== false && props.target) { extraHash.target = props.target; }
228286
if (props.invalidation?.file !== false && props.file) { extraHash.file = props.file; }
229287
if (props.invalidation?.repositoryName !== false && props.repositoryName) { extraHash.repositoryName = props.repositoryName; }
288+
if (props.invalidation?.networkMode !== false && props.networkMode) { extraHash.networkMode = props.networkMode; }
230289

231290
// add "salt" to the hash in order to invalidate the image in the upgrade to
232291
// 1.21.0 which removes the AdoptedRepository resource (and will cause the
@@ -258,6 +317,7 @@ export class DockerImageAsset extends CoreConstruct implements IAsset {
258317
dockerBuildTarget: this.dockerBuildTarget,
259318
dockerFile: props.file,
260319
sourceHash: staging.assetHash,
320+
networkMode: props.networkMode?.mode,
261321
});
262322

263323
this.repository = ecr.Repository.fromRepositoryName(this, 'Repository', location.repositoryName);

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

+15-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { describeDeprecated, testDeprecated, testFutureBehavior } from '@aws-cdk
66
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
77
import { App, DefaultStackSynthesizer, IgnoreMode, Lazy, LegacyStackSynthesizer, Stack, Stage } from '@aws-cdk/core';
88
import * as cxapi from '@aws-cdk/cx-api';
9-
import { DockerImageAsset } from '../lib';
9+
import { DockerImageAsset, NetworkMode } from '../lib';
1010

1111
/* eslint-disable quote-props */
1212

@@ -147,6 +147,20 @@ describe('image asset', () => {
147147

148148
});
149149

150+
testFutureBehavior('with networkMode', flags, App, (app) => {
151+
// GIVEN
152+
const stack = new Stack(app);
153+
// WHEN
154+
new DockerImageAsset(stack, 'Image', {
155+
directory: path.join(__dirname, 'demo-image'),
156+
networkMode: NetworkMode.DEFAULT,
157+
});
158+
159+
// THEN
160+
const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET);
161+
expect(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).networkMode).toEqual('default');
162+
});
163+
150164
testFutureBehavior('asset.repository.grantPull can be used to grant a principal permissions to use the image', flags, App, (app) => {
151165
// GIVEN
152166
const stack = new Stack(app);

packages/@aws-cdk/cloud-assembly-schema/lib/assets/docker-image-asset.ts

+9
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ export interface DockerImageSource {
6262
* @default - No additional build arguments
6363
*/
6464
readonly dockerBuildArgs?: { [name: string]: string };
65+
66+
/**
67+
* Networking mode for the RUN commands during build. _Requires Docker Engine API v1.25+_.
68+
*
69+
* Specify this property to build images on a specific networking mode.
70+
*
71+
* @default - no networking mode specified
72+
*/
73+
readonly networkMode?: string;
6574
}
6675

6776
/**

packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts

+7
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ export interface ContainerImageAssetMetadataEntry extends BaseAssetMetadataEntry
131131
* @default - no file is passed
132132
*/
133133
readonly file?: string;
134+
135+
/**
136+
* Networking mode for the RUN commands during build.
137+
*
138+
* @default - no networking mode specified
139+
*/
140+
readonly networkMode?: string;
134141
}
135142

136143
/**

packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@
154154
"additionalProperties": {
155155
"type": "string"
156156
}
157+
},
158+
"networkMode": {
159+
"description": "Networking mode for the RUN commands during build. _Requires Docker Engine API v1.25+_. (Default - no networking mode specified)",
160+
"type": "string"
157161
}
158162
}
159163
},
@@ -189,4 +193,4 @@
189193
}
190194
},
191195
"$schema": "http://json-schema.org/draft-07/schema#"
192-
}
196+
}

packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,10 @@
226226
"description": "Path to the Dockerfile (relative to the directory). (Default - no file is passed)",
227227
"type": "string"
228228
},
229+
"networkMode": {
230+
"description": "Networking mode for the RUN commands during build. _Requires Docker Engine API v1.25+_. (Default - no networking mode specified)",
231+
"type": "string"
232+
},
229233
"id": {
230234
"description": "Logical identifier for the asset",
231235
"type": "string"
@@ -870,4 +874,4 @@
870874
}
871875
},
872876
"$schema": "http://json-schema.org/draft-07/schema#"
873-
}
877+
}

packages/@aws-cdk/core/lib/assets.ts

+9
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,15 @@ export interface DockerImageAssetSource {
203203
* @deprecated repository name should be specified at the environment-level and not at the image level
204204
*/
205205
readonly repositoryName?: string;
206+
207+
/**
208+
* Networking mode for the RUN commands during build. _Requires Docker Engine API v1.25+_.
209+
*
210+
* Specify this property to build images on a specific networking mode.
211+
*
212+
* @default - no networking mode specified
213+
*/
214+
readonly networkMode?: string;
206215
}
207216

208217
/**

packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts

+1
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ export class DefaultStackSynthesizer extends StackSynthesizer {
424424
dockerBuildArgs: asset.dockerBuildArgs,
425425
dockerBuildTarget: asset.dockerBuildTarget,
426426
dockerFile: asset.dockerFile,
427+
networkMode: asset.networkMode,
427428
},
428429
destinations: {
429430
[this.manifestEnvName]: {

packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts

+1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export class LegacyStackSynthesizer extends StackSynthesizer {
136136
buildArgs: asset.dockerBuildArgs,
137137
target: asset.dockerBuildTarget,
138138
file: asset.dockerFile,
139+
networkMode: asset.networkMode,
139140
};
140141

141142
this.stack.node.addMetadata(cxschema.ArtifactMetadataEntryType.ASSET, metadata);

packages/aws-cdk/lib/assets.ts

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ async function prepareDockerImageAsset(
122122
dockerBuildArgs: asset.buildArgs,
123123
dockerBuildTarget: asset.target,
124124
dockerFile: asset.file,
125+
networkMode: asset.networkMode,
125126
}, {
126127
repositoryName,
127128
imageTag,

packages/cdk-assets/lib/private/docker.ts

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface BuildOptions {
1414
readonly target?: string;
1515
readonly file?: string;
1616
readonly buildArgs?: Record<string, string>;
17+
readonly networkMode?: string;
1718
}
1819

1920
export interface DockerCredentialsConfig {
@@ -53,6 +54,7 @@ export class Docker {
5354
'--tag', options.tag,
5455
...options.target ? ['--target', options.target] : [],
5556
...options.file ? ['--file', options.file] : [],
57+
...options.networkMode ? ['--network', options.networkMode] : [],
5658
'.',
5759
];
5860
await this.execute(buildCommand, { cwd: options.directory });

packages/cdk-assets/lib/private/handlers/container-images.ts

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export class ContainerImageAssetHandler implements IAssetHandler {
125125
buildArgs: source.dockerBuildArgs,
126126
target: source.dockerBuildTarget,
127127
file: source.dockerFile,
128+
networkMode: source.networkMode,
128129
});
129130
}
130131

packages/cdk-assets/test/docker-images.test.ts

+44
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,26 @@ beforeEach(() => {
7373
},
7474
},
7575
}),
76+
'/default-network/cdk.out/assets.json': JSON.stringify({
77+
version: Manifest.version(),
78+
dockerImages: {
79+
theAsset: {
80+
source: {
81+
directory: 'dockerdir',
82+
networkMode: 'default',
83+
},
84+
destinations: {
85+
theDestination: {
86+
region: 'us-north-50',
87+
assumeRoleArn: 'arn:aws:role',
88+
repositoryName: 'repo',
89+
imageTag: 'nopqr',
90+
},
91+
},
92+
},
93+
},
94+
}),
95+
'/default-network/cdk.out/dockerdir/Dockerfile': 'FROM scratch',
7696
});
7797

7898
aws = mockAws();
@@ -164,6 +184,30 @@ describe('with a complete manifest', () => {
164184
expectAllSpawns();
165185
expect(true).toBeTruthy(); // Expect no exception, satisfy linter
166186
});
187+
188+
test('build with networkMode option', async () => {
189+
pub = new AssetPublishing(AssetManifest.fromPath('/default-network/cdk.out'), { aws });
190+
const defaultNetworkDockerpath = '/default-network/cdk.out/dockerdir';
191+
aws.mockEcr.describeImages = mockedApiFailure('ImageNotFoundException', 'File does not exist');
192+
aws.mockEcr.getAuthorizationToken = mockedApiResult({
193+
authorizationData: [
194+
{ authorizationToken: 'dXNlcjpwYXNz', proxyEndpoint: 'https://proxy.com/' },
195+
],
196+
});
197+
198+
const expectAllSpawns = mockSpawn(
199+
{ commandLine: ['docker', 'login', '--username', 'user', '--password-stdin', 'https://proxy.com/'] },
200+
{ commandLine: ['docker', 'inspect', 'cdkasset-theasset'], exitCode: 1 },
201+
{ commandLine: ['docker', 'build', '--tag', 'cdkasset-theasset', '--network', 'default', '.'], cwd: defaultNetworkDockerpath },
202+
{ commandLine: ['docker', 'tag', 'cdkasset-theasset', '12345.amazonaws.com/repo:nopqr'] },
203+
{ commandLine: ['docker', 'push', '12345.amazonaws.com/repo:nopqr'] },
204+
);
205+
206+
await pub.publish();
207+
208+
expectAllSpawns();
209+
expect(true).toBeTruthy(); // Expect no exception, satisfy linter
210+
});
167211
});
168212

169213
describe('external assets', () => {

0 commit comments

Comments
 (0)