Skip to content

Commit a162735

Browse files
committed
refactor dev to use normalised config
1 parent 1c3384d commit a162735

File tree

9 files changed

+77
-116
lines changed

9 files changed

+77
-116
lines changed

packages/containers-shared/src/build.ts

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import { spawn } from "child_process";
22
import { readFileSync } from "fs";
3-
import path from "path";
4-
import { BuildArgs, ContainerDevOptions, Logger } from "./types";
3+
import { BuildArgs, DockerfileConfig, Logger } from "./types";
54

65
export async function constructBuildCommand(
76
options: BuildArgs,
8-
/** wrangler config path. used to resolve relative dockerfile path */
9-
configPath: string | undefined,
107
logger?: Logger
118
) {
129
const platform = options.platform ?? "linux/amd64";
@@ -27,12 +24,11 @@ export async function constructBuildCommand(
2724
if (options.setNetworkToHost) {
2825
buildCmd.push("--network", "host");
2926
}
30-
const baseDir = configPath ? path.dirname(configPath) : process.cwd();
31-
const absDockerfilePath = path.resolve(baseDir, options.pathToDockerfile);
32-
const dockerfile = readFileSync(absDockerfilePath, "utf-8");
27+
28+
const dockerfile = readFileSync(options.pathToDockerfile, "utf-8");
3329
// pipe in the dockerfile
3430
buildCmd.push("-f", "-");
35-
buildCmd.push(options.buildContext ?? path.dirname(absDockerfilePath));
31+
buildCmd.push(options.buildContext);
3632
logger?.debug(`Building image with command: ${buildCmd.join(" ")}`);
3733
return { buildCmd, dockerfile };
3834
}
@@ -92,20 +88,16 @@ export function dockerBuild(
9288

9389
export async function buildImage(
9490
dockerPath: string,
95-
options: ContainerDevOptions,
96-
configPath: string | undefined
91+
options: DockerfileConfig,
92+
imageTag: string
9793
) {
98-
// just let the tag default to latest
99-
const { buildCmd, dockerfile } = await constructBuildCommand(
100-
{
101-
tag: options.imageTag,
102-
pathToDockerfile: options.image,
103-
buildContext: options.imageBuildContext,
104-
args: options.args,
105-
platform: "linux/amd64",
106-
},
107-
configPath
108-
);
94+
const { buildCmd, dockerfile } = await constructBuildCommand({
95+
tag: imageTag,
96+
pathToDockerfile: options.dockerfile,
97+
buildContext: options.image_build_context,
98+
args: options.image_vars,
99+
platform: "linux/amd64",
100+
});
109101

110102
return dockerBuild(dockerPath, { buildCmd, dockerfile });
111103
}

packages/containers-shared/src/images.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
11
import { buildImage } from "./build";
22
import {
33
getCloudflareContainerRegistry,
4+
getDevContainerImageName,
45
isCloudflareRegistryLink,
56
} from "./knobs";
67
import { dockerLoginManagedRegistry } from "./login";
7-
import { ContainerDevOptions } from "./types";
8+
import { ContainerNormalisedConfig, RegistryLinkConfig } from "./types";
89
import {
910
checkExposedPorts,
10-
isDockerfile,
1111
runDockerCmd,
1212
verifyDockerInstalled,
1313
} from "./utils";
1414

1515
export async function pullImage(
1616
dockerPath: string,
17-
options: ContainerDevOptions
17+
options: RegistryLinkConfig,
18+
tag: string
1819
): Promise<{ abort: () => void; ready: Promise<void> }> {
1920
await dockerLoginManagedRegistry(dockerPath);
2021
const pull = runDockerCmd(dockerPath, [
2122
"pull",
22-
options.image,
23+
options.registry_link,
2324
// All containers running on our platform need to be built for amd64 architecture, but by default docker pull seems to look for an image matching the host system, so we need to specify this here
2425
"--platform",
2526
"linux/amd64",
2627
]);
2728
const ready = pull.ready.then(async ({ aborted }: { aborted: boolean }) => {
2829
if (!aborted) {
2930
// re-tag image with the expected dev-formatted image tag for consistency
30-
await runDockerCmd(dockerPath, ["tag", options.image, options.imageTag]);
31+
await runDockerCmd(dockerPath, ["tag", options.registry_link, tag]);
3132
}
3233
});
3334

@@ -51,14 +52,14 @@ export async function pullImage(
5152
*/
5253
export async function prepareContainerImagesForDev(
5354
dockerPath: string,
54-
containerOptions: ContainerDevOptions[],
55-
configPath: string | undefined,
55+
containerOptions: ContainerNormalisedConfig[],
56+
containerBuildId: string,
5657
onContainerImagePreparationStart: (args: {
57-
containerOptions: ContainerDevOptions;
58+
containerOptions: ContainerNormalisedConfig;
5859
abort: () => void;
5960
}) => void,
6061
onContainerImagePreparationEnd: (args: {
61-
containerOptions: ContainerDevOptions;
62+
containerOptions: ContainerNormalisedConfig;
6263
}) => void
6364
) {
6465
let aborted = false;
@@ -69,8 +70,9 @@ export async function prepareContainerImagesForDev(
6970
}
7071
await verifyDockerInstalled(dockerPath);
7172
for (const options of containerOptions) {
72-
if (isDockerfile(options.image, configPath)) {
73-
const build = await buildImage(dockerPath, options, configPath);
73+
const tag = getDevContainerImageName(options.class_name, containerBuildId);
74+
if ("dockerfile" in options) {
75+
const build = await buildImage(dockerPath, options, tag);
7476
onContainerImagePreparationStart({
7577
containerOptions: options,
7678
abort: () => {
@@ -83,13 +85,13 @@ export async function prepareContainerImagesForDev(
8385
containerOptions: options,
8486
});
8587
} else {
86-
if (!isCloudflareRegistryLink(options.image)) {
88+
if (!isCloudflareRegistryLink(options.registry_link)) {
8789
throw new Error(
88-
`Image "${options.image}" is a registry link but does not point to the Cloudflare container registry.\n` +
90+
`Image "${options.registry_link}" is a registry link but does not point to the Cloudflare container registry.\n` +
8991
`To use an existing image from another repository, see https://developers.cloudflare.com/containers/image-management/#using-existing-images`
9092
);
9193
}
92-
const pull = await pullImage(dockerPath, options);
94+
const pull = await pullImage(dockerPath, options, tag);
9395
onContainerImagePreparationStart({
9496
containerOptions: options,
9597
abort: () => {
@@ -103,7 +105,7 @@ export async function prepareContainerImagesForDev(
103105
});
104106
}
105107
if (!aborted) {
106-
await checkExposedPorts(dockerPath, options);
108+
await checkExposedPorts(dockerPath, options, tag);
107109
}
108110
}
109111
}

packages/containers-shared/src/types.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export type BuildArgs = {
1717
tag: string;
1818
pathToDockerfile: string;
1919
/** image_build_context or args.PATH. if not provided, defaults to the dockerfile directory */
20-
buildContext?: string;
20+
buildContext: string;
2121
/** any env vars that should be passed in at build time */
2222
args?: Record<string, string>;
2323
/** platform to build for. defaults to linux/amd64 */
@@ -50,19 +50,6 @@ export type InstanceTypeOrLimits =
5050
instance_type: InstanceType;
5151
};
5252

53-
/** build/pull agnostic container options */
54-
export type ContainerDevOptions = {
55-
/** may be dockerfile or registry link */
56-
image: string;
57-
/** formatted as cloudflare-dev/workername-DOclassname:build-id */
58-
imageTag: string;
59-
/** container's DO class name */
60-
class_name: string;
61-
imageBuildContext?: string;
62-
/** build time args */
63-
args?: Record<string, string>;
64-
};
65-
6653
/**
6754
* Shared container config that is used regardless of whether the image is from a dockerfile or a registry link.
6855
*/

packages/containers-shared/src/utils.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { execFile, spawn, StdioOptions } from "child_process";
22
import { existsSync, statSync } from "fs";
33
import path from "path";
44
import { dockerImageInspect } from "./inspect";
5-
import { ContainerDevOptions } from "./types";
5+
import { ContainerNormalisedConfig } from "./types";
66

77
/** helper for simple docker command call that don't require any io handling */
88
export const runDockerCmd = (
@@ -201,10 +201,11 @@ const getContainerIdsFromImage = async (
201201
*/
202202
export async function checkExposedPorts(
203203
dockerPath: string,
204-
options: ContainerDevOptions
204+
options: ContainerNormalisedConfig,
205+
imageTag: string
205206
) {
206207
const output = await dockerImageInspect(dockerPath, {
207-
imageTag: options.imageTag,
208+
imageTag: imageTag,
208209
formatString: "{{ len .Config.ExposedPorts }}",
209210
});
210211
if (output === "0") {

packages/containers-shared/tests/utils.test.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { mkdirSync, writeFileSync } from "fs";
2+
import { ContainerNormalisedConfig } from "../src/types";
23
import { checkExposedPorts, isDockerfile } from "./../src/utils";
34
import { runInTempDir } from "./helpers/run-in-tmp-dir";
45

@@ -71,6 +72,10 @@ vi.mock("../src/inspect", async (importOriginal) => {
7172
};
7273
});
7374

75+
const containerConfig = {
76+
dockerfile: "",
77+
class_name: "MyContainer",
78+
} as ContainerNormalisedConfig;
7479
describe("checkExposedPorts", () => {
7580
beforeEach(() => {
7681
docketImageInspectResult = "1";
@@ -79,23 +84,14 @@ describe("checkExposedPorts", () => {
7984
it("should not error when some ports are exported", async () => {
8085
docketImageInspectResult = "1";
8186
await expect(
82-
checkExposedPorts("./container-context/Dockerfile", {
83-
image: "",
84-
imageTag: "",
85-
class_name: "MyContainer",
86-
})
87+
checkExposedPorts("docker", containerConfig, "image:tag")
8788
).resolves.toBeUndefined();
8889
});
8990

9091
it("should error, with an appropriate message when no ports are exported", async () => {
9192
docketImageInspectResult = "0";
92-
expect(
93-
checkExposedPorts("./container-context/Dockerfile", {
94-
image: "",
95-
imageTag: "",
96-
class_name: "MyContainer",
97-
})
98-
).rejects.toThrowErrorMatchingInlineSnapshot(`
93+
expect(checkExposedPorts("docker", containerConfig, "image:tag")).rejects
94+
.toThrowErrorMatchingInlineSnapshot(`
9995
[Error: The container "MyContainer" does not expose any ports. In your Dockerfile, please expose any ports you intend to connect to.
10096
For additional information please see: https://developers.cloudflare.com/containers/local-dev/#exposing-ports.
10197
]

packages/wrangler/src/api/startDevWorker/ConfigController.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import assert from "node:assert";
22
import path from "node:path";
3-
import { isDockerfile } from "@cloudflare/containers-shared";
43
import { watch } from "chokidar";
54
import { getAssetsOptions, validateAssetsArgsAndConfig } from "../../assets";
65
import { fillOpenAPIConfiguration } from "../../cloudchamber/common";
76
import { readConfig } from "../../config";
87
import { containersScope } from "../../containers";
8+
import { getNormalizedContainerOptions } from "../../containers/config";
99
import { getEntry } from "../../deployment-bundle/entry";
1010
import {
1111
getBindings,
@@ -347,7 +347,7 @@ async function resolveConfig(
347347
tsconfig: input.build?.tsconfig ?? config.tsconfig,
348348
exports: entry.exports,
349349
},
350-
containers: config.containers,
350+
containers: await getNormalizedContainerOptions(config),
351351
dev: await resolveDevConfig(config, input),
352352
legacy: {
353353
site: legacySite,
@@ -399,10 +399,10 @@ async function resolveConfig(
399399
// for pulling containers, we need to make sure the OpenAPI config for the
400400
// container API client is properly set so that we can get the correct permissions
401401
// from the cloudchamber API to pull from the repository.
402-
const needsPulling = resolved.containers?.some(
403-
(c) => !isDockerfile(c.image ?? c.configuration?.image, config.configPath)
402+
const needsPulling = resolved.containers.some(
403+
(c) => "registry_link" in c && c.registry_link
404404
);
405-
if (needsPulling && !resolved.dev.remote) {
405+
if (resolved.containers.length && needsPulling && !resolved.dev.remote) {
406406
await fillOpenAPIConfiguration(config, containersScope);
407407
}
408408

packages/wrangler/src/api/startDevWorker/LocalRuntimeController.ts

Lines changed: 20 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import type {
2727
ReloadStartEvent,
2828
} from "./events";
2929
import type { Binding, File, StartDevWorkerOptions } from "./types";
30-
import type { ContainerDevOptions } from "@cloudflare/containers-shared";
30+
import type { ContainerNormalisedConfig } from "@cloudflare/containers-shared";
3131

3232
async function getBinaryFileContents(file: File<string | Uint8Array>) {
3333
if ("contents" in file) {
@@ -183,7 +183,7 @@ export class LocalRuntimeController extends RuntimeController {
183183
// Used to store the information and abort handle for the
184184
// current container that is being built
185185
containerBeingBuilt?: {
186-
containerOptions: ContainerDevOptions;
186+
containerOptions: ContainerNormalisedConfig;
187187
abort: () => void;
188188
abortRequested: boolean;
189189
};
@@ -219,21 +219,30 @@ export class LocalRuntimeController extends RuntimeController {
219219
}
220220

221221
// Assemble container options and build if necessary
222-
const containerOptions = await getContainerOptions(data.config);
223-
this.#dockerPath = data.config.dev?.dockerPath ?? getDockerPath();
224-
// keep track of them so we can clean up later
225-
for (const container of containerOptions ?? []) {
226-
this.#containerImageTagsSeen.add(container.imageTag);
227-
}
222+
228223
if (
229-
containerOptions &&
224+
data.config.containers?.length &&
225+
data.config.dev.enableContainers &&
230226
this.#currentContainerBuildId !== data.config.dev.containerBuildId
231227
) {
228+
this.#dockerPath = data.config.dev?.dockerPath ?? getDockerPath();
229+
assert(
230+
data.config.dev.containerBuildId,
231+
"Build ID should be set if containers are enabled and defined"
232+
);
233+
for (const container of data.config.containers) {
234+
this.#containerImageTagsSeen.add(
235+
getDevContainerImageName(
236+
container.class_name,
237+
data.config.dev.containerBuildId
238+
)
239+
);
240+
}
232241
logger.log(chalk.dim("⎔ Preparing container image(s)..."));
233242
await prepareContainerImagesForDev(
234243
this.#dockerPath,
235-
containerOptions,
236-
data.config.config,
244+
data.config.containers,
245+
data.config.dev.containerBuildId,
237246
(buildStartEvent) => {
238247
this.containerBeingBuilt = {
239248
...buildStartEvent,
@@ -421,35 +430,3 @@ export class LocalRuntimeController extends RuntimeController {
421430
this.emit("reloadComplete", data);
422431
}
423432
}
424-
425-
/**
426-
* @returns Container options suitable for building or pulling images,
427-
* with image tag set to well-known dev format.
428-
* Undefined if containers are not enabled or not configured.
429-
*/
430-
export async function getContainerOptions(
431-
config: BundleCompleteEvent["config"]
432-
) {
433-
if (!config.containers?.length || config.dev.enableContainers === false) {
434-
return undefined;
435-
}
436-
// should be defined if containers are enabled
437-
assert(
438-
config.dev.containerBuildId,
439-
"Build ID should be set if containers are enabled and defined"
440-
);
441-
const containers: ContainerDevOptions[] = [];
442-
for (const container of config.containers) {
443-
containers.push({
444-
image: container.image ?? container.configuration?.image,
445-
imageTag: getDevContainerImageName(
446-
container.class_name,
447-
config.dev.containerBuildId
448-
),
449-
args: container.image_vars,
450-
imageBuildContext: container.image_build_context,
451-
class_name: container.class_name,
452-
});
453-
}
454-
return containers;
455-
}

0 commit comments

Comments
 (0)