Skip to content

Commit c6b6acd

Browse files
committed
refactor containers config handling
Unfortunately I had to squash this to fix merge conflicts, so this commit contains: * add config normalisation function * refactor dev to use normalised config * refactor containers deploy to use normalised config * consolidate tests into containers/deploy.test.ts * fix up loop in build command and config handling in ensureDiskSize * stray fixups for cloudchamber apply
1 parent 7d14000 commit c6b6acd

File tree

31 files changed

+2727
-3929
lines changed

31 files changed

+2727
-3929
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
root: true,
3+
extends: ["@cloudflare/eslint-config-worker"],
4+
};

packages/containers-shared/.eslintrc.json

Lines changed: 0 additions & 23 deletions
This file was deleted.

packages/containers-shared/src/build.ts

Lines changed: 12 additions & 24 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 type { BuildArgs, ContainerDevOptions, Logger } from "./types";
3+
import type { 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,16 +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");
3327

34-
const absBuildContext = options.buildContext
35-
? path.resolve(baseDir, options.buildContext)
36-
: path.dirname(absDockerfilePath);
28+
const dockerfile = readFileSync(options.pathToDockerfile, "utf-8");
3729
// pipe in the dockerfile
3830
buildCmd.push("-f", "-");
39-
buildCmd.push(absBuildContext);
31+
buildCmd.push(options.buildContext);
4032
logger?.debug(`Building image with command: ${buildCmd.join(" ")}`);
4133
return { buildCmd, dockerfile };
4234
}
@@ -96,20 +88,16 @@ export function dockerBuild(
9688

9789
export async function buildImage(
9890
dockerPath: string,
99-
options: ContainerDevOptions,
100-
configPath: string | undefined
91+
options: DockerfileConfig,
92+
imageTag: string
10193
) {
102-
// just let the tag default to latest
103-
const { buildCmd, dockerfile } = await constructBuildCommand(
104-
{
105-
tag: options.imageTag,
106-
pathToDockerfile: options.image,
107-
buildContext: options.imageBuildContext,
108-
args: options.args,
109-
platform: "linux/amd64",
110-
},
111-
configPath
112-
);
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+
});
113101

114102
return dockerBuild(dockerPath, { buildCmd, dockerfile });
115103
}

packages/containers-shared/src/images.ts

Lines changed: 16 additions & 16 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 { type ContainerDevOptions } from "./types";
88
import {
99
checkExposedPorts,
10-
isDockerfile,
1110
runDockerCmd,
1211
verifyDockerInstalled,
1312
} from "./utils";
13+
import type { ContainerNormalisedConfig, RegistryLinkConfig } from "./types";
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,19 +52,17 @@ export async function pullImage(
5152
*/
5253
export async function prepareContainerImagesForDev(options: {
5354
dockerPath: string;
54-
configPath?: string;
55-
containerOptions: ContainerDevOptions[];
55+
containerOptions: ContainerNormalisedConfig[];
5656
onContainerImagePreparationStart: (args: {
57-
containerOptions: ContainerDevOptions;
57+
containerOptions: ContainerNormalisedConfig;
5858
abort: () => void;
5959
}) => void;
6060
onContainerImagePreparationEnd: (args: {
61-
containerOptions: ContainerDevOptions;
61+
containerOptions: ContainerNormalisedConfig[];
6262
}) => void;
6363
}) {
6464
const {
6565
dockerPath,
66-
configPath,
6766
containerOptions,
6867
onContainerImagePreparationStart,
6968
onContainerImagePreparationEnd,
@@ -76,8 +75,9 @@ export async function prepareContainerImagesForDev(options: {
7675
}
7776
await verifyDockerInstalled(dockerPath);
7877
for (const options of containerOptions) {
79-
if (isDockerfile(options.image, configPath)) {
80-
const build = await buildImage(dockerPath, options, configPath);
78+
const tag = getDevContainerImageName(options.class_name, containerBuildId);
79+
if ("dockerfile" in options) {
80+
const build = await buildImage(dockerPath, options, tag);
8181
onContainerImagePreparationStart({
8282
containerOptions: options,
8383
abort: () => {
@@ -90,13 +90,13 @@ export async function prepareContainerImagesForDev(options: {
9090
containerOptions: options,
9191
});
9292
} else {
93-
if (!isCloudflareRegistryLink(options.image)) {
93+
if (!isCloudflareRegistryLink(options.registry_link)) {
9494
throw new Error(
95-
`Image "${options.image}" is a registry link but does not point to the Cloudflare container registry.\n` +
95+
`Image "${options.registry_link}" is a registry link but does not point to the Cloudflare container registry.\n` +
9696
`To use an existing image from another repository, see https://developers.cloudflare.com/containers/image-management/#using-existing-images`
9797
);
9898
}
99-
const pull = await pullImage(dockerPath, options);
99+
const pull = await pullImage(dockerPath, options, tag);
100100
onContainerImagePreparationStart({
101101
containerOptions: options,
102102
abort: () => {
@@ -110,7 +110,7 @@ export async function prepareContainerImagesForDev(options: {
110110
});
111111
}
112112
if (!aborted) {
113-
await checkExposedPorts(dockerPath, options);
113+
await checkExposedPorts(dockerPath, options, tag);
114114
}
115115
}
116116
}
Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { InstanceType, SchedulingPolicy } from "./client";
2+
13
export interface Logger {
24
debug: (message: string) => void;
35
log: (message: string) => void;
@@ -11,7 +13,7 @@ export type BuildArgs = {
1113
tag: string;
1214
pathToDockerfile: string;
1315
/** image_build_context or args.PATH. if not provided, defaults to the dockerfile directory */
14-
buildContext?: string;
16+
buildContext: string;
1517
/** any env vars that should be passed in at build time */
1618
args?: Record<string, string>;
1719
/** platform to build for. defaults to linux/amd64 */
@@ -20,15 +22,50 @@ export type BuildArgs = {
2022
setNetworkToHost?: boolean;
2123
};
2224

23-
/** build/pull agnostic container options */
24-
export type ContainerDevOptions = {
25-
/** may be dockerfile or registry link */
26-
image: string;
27-
/** formatted as cloudflare-dev/workername-DOclassname:build-id */
28-
imageTag: string;
25+
export type ContainerNormalisedConfig = RegistryLinkConfig | DockerfileConfig;
26+
export type DockerfileConfig = SharedContainerConfig & {
27+
/** absolute path, resolved relative to the wrangler config file */
28+
dockerfile: string;
29+
/** absolute path, resolved relative to the wrangler config file. defaults to the directory of the dockerfile */
30+
image_build_context: string;
31+
image_vars?: Record<string, string>;
32+
};
33+
export type RegistryLinkConfig = SharedContainerConfig & {
34+
registry_link: string;
35+
};
36+
37+
export type InstanceTypeOrLimits =
38+
| {
39+
/** if undefined in config, defaults to instance_type */
40+
/** disk size is defined in config in mb but normalised here to bytes */
41+
disk_bytes?: number;
42+
vcpu?: number;
43+
memory_mib?: number;
44+
}
45+
| {
46+
/** if undefined in config, defaults to "dev" */
47+
instance_type: InstanceType;
48+
};
49+
50+
/**
51+
* Shared container config that is used regardless of whether the image is from a dockerfile or a registry link.
52+
*/
53+
export type SharedContainerConfig = {
54+
/** if undefined in config, defaults to worker_name[-envName]-class_name. */
55+
name: string;
2956
/** container's DO class name */
3057
class_name: string;
31-
imageBuildContext?: string;
32-
/** build time args */
33-
args?: Record<string, string>;
34-
};
58+
/** if undefined in config, defaults to 0 */
59+
max_instances: number;
60+
/** if undefined in config, defaults to "default" */
61+
scheduling_policy: SchedulingPolicy;
62+
/** if undefined in config, defaults to 25 */
63+
rollout_step_percentage: number;
64+
/** if undefined in config, defaults to "full_auto" */
65+
rollout_kind: "full_auto" | "full_manual" | "none";
66+
constraints?: {
67+
regions?: string[];
68+
cities?: string[];
69+
tier?: number;
70+
};
71+
} & InstanceTypeOrLimits;

packages/containers-shared/src/utils.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { execFile, spawn } from "child_process";
2-
import { randomUUID } from "crypto";
32
import { existsSync, statSync } from "fs";
43
import path from "path";
54
import { dockerImageInspect } from "./inspect";
6-
import { type ContainerDevOptions } from "./types";
5+
import type { ContainerNormalisedConfig } from "./types";
76
import type { StdioOptions } from "child_process";
87

98
/** helper for simple docker command call that don't require any io handling */
@@ -57,7 +56,7 @@ export const runDockerCmd = (
5756
}
5857
},
5958
ready,
60-
then: async (resolve, reject) => ready.then(resolve).catch(reject),
59+
then: async (onResolve, onReject) => ready.then(onResolve).catch(onReject),
6160
};
6261
};
6362

@@ -96,8 +95,8 @@ export const verifyDockerInstalled = async (
9695
}
9796
};
9897

99-
export function isDir(path: string) {
100-
const stats = statSync(path);
98+
export function isDir(inputPath: string) {
99+
const stats = statSync(inputPath);
101100
return stats.isDirectory();
102101
}
103102

@@ -176,7 +175,7 @@ export const cleanupContainers = async (
176175
["inherit", "pipe", "pipe"]
177176
);
178177
return true;
179-
} catch (error) {
178+
} catch {
180179
return false;
181180
}
182181
};
@@ -230,10 +229,11 @@ export const getContainerIdsFromImage = async (
230229
*/
231230
export async function checkExposedPorts(
232231
dockerPath: string,
233-
options: ContainerDevOptions
232+
options: ContainerNormalisedConfig,
233+
imageTag: string
234234
) {
235235
const output = await dockerImageInspect(dockerPath, {
236-
imageTag: options.imageTag,
236+
imageTag: imageTag,
237237
formatString: "{{ len .Config.ExposedPorts }}",
238238
});
239239
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/containers-shared/tsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
"allowSyntheticDefaultImports": true,
88
"experimentalDecorators": true,
99
"skipLibCheck": true,
10-
"outDir": "./dist",
11-
"types": ["node"]
10+
"types": ["node"],
11+
"noUncheckedIndexedAccess": true
1212
},
1313
"include": ["src/**/*", "index.ts"],
1414
"exclude": ["dist", "node_modules"]

0 commit comments

Comments
 (0)