Skip to content

Commit 118945b

Browse files
authored
add dev.enable_containers and docker_path to miniflare/wrangler (#9500)
* add ignore_containers and dockerpath * extra test * lint * pr feedback * rename ContainerService to ContainerController * rename to `enable_containers` to prevent confusion on double negatives
1 parent f3e1587 commit 118945b

File tree

21 files changed

+231
-91
lines changed

21 files changed

+231
-91
lines changed

fixtures/container-app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"wrangler": "workspace:*"
2020
},
2121
"volta": {
22+
"node": "20.19.2",
2223
"extends": "../../package.json"
2324
}
2425
}
Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,62 @@
11
import { resolve } from "node:path";
2-
import dedent from "ts-dedent";
3-
import { afterAll, beforeAll, describe, it } from "vitest";
2+
import { beforeEach, describe, expect, it, vi } from "vitest";
43
import { runWranglerDev } from "../../shared/src/run-wrangler-long-lived";
54

6-
describe("Containers local dev", () => {
7-
let stop: (() => Promise<unknown>) | undefined, getOutput: () => string;
5+
// TODO: we'll want to run this on all OSes but that will require some setup because docker is not installed by default on macos and windows
6+
describe.skipIf(process.platform !== "linux" && process.env.CI === "true")(
7+
"Containers local dev",
8+
() => {
9+
it("starts up container service if containers are in config", async () => {
10+
const { stop, getOutput } = await runWranglerDev(
11+
resolve(__dirname, ".."),
12+
["--port=0", "--inspector-port=0"]
13+
);
14+
expect(getOutput()).toContain(`Hello from ContainerController!`);
15+
await stop?.();
16+
});
817

9-
beforeAll(async () => {
10-
({ stop, getOutput } = await runWranglerDev(resolve(__dirname, ".."), [
11-
"--port=0",
12-
"--inspector-port=0",
13-
]));
14-
});
18+
it("doesn't start up container service if no containers are present", async () => {
19+
const { stop, getOutput } = await runWranglerDev(
20+
resolve(__dirname, ".."),
21+
["--port=0", "--inspector-port=0", "-c=wrangler.no-containers.jsonc"]
22+
);
23+
const output = getOutput();
24+
expect(output).not.toContain("Hello from ContainerController!");
25+
await stop?.();
26+
});
1527

16-
afterAll(async () => {
17-
await stop?.();
18-
});
28+
it("doesn't start up container service if enable_containers is set to false via config", async () => {
29+
const { stop, getOutput } = await runWranglerDev(
30+
resolve(__dirname, ".."),
31+
[
32+
"--port=0",
33+
"--inspector-port=0",
34+
"-c=wrangler.enable-containers.jsonc",
35+
]
36+
);
37+
const output = getOutput();
38+
expect(output).not.toContain("Hello from ContainerController!");
39+
await stop?.();
40+
});
1941

20-
it("starts up container service if containers are in config", async ({
21-
expect,
22-
}) => {
23-
expect(getOutput()).toContain(dedent`
24-
Hello from ContainerService!
25-
Container Options: {
26-
"Container": {
27-
"image": "./Dockerfile",
28-
"maxInstances": 2
29-
}
30-
}
31-
`);
32-
});
42+
it("doesn't start up container service if --enable-containers is set to false via CLI", async () => {
43+
const { stop, getOutput } = await runWranglerDev(
44+
resolve(__dirname, ".."),
45+
["--port=0", "--inspector-port=0", "--enable-containers=false"]
46+
);
47+
expect(getOutput()).not.toContain(`Hello from ContainerController!`);
48+
await stop?.();
49+
});
3350

34-
it("doesn't start up container service if no containers are present", async ({
35-
expect,
36-
}) => {
37-
await stop?.();
38-
({ stop, getOutput } = await runWranglerDev(resolve(__dirname, ".."), [
39-
"--port=0",
40-
"--inspector-port=0",
41-
"-c=wrangler.no-containers.jsonc",
42-
]));
43-
expect(getOutput()).not.toContain(`Hello from ContainerService!`);
44-
expect(getOutput()).toContain("Ready on");
45-
});
46-
});
51+
// TODO: not entirely sure how to make this test work without logging random stuff
52+
it("gets docker path from env var", async () => {
53+
vi.stubEnv("WRANGLER_CONTAINERS_DOCKER_PATH", "blah/docker");
54+
const { stop, getOutput } = await runWranglerDev(
55+
resolve(__dirname, ".."),
56+
["--port=0", "--inspector-port=0"]
57+
);
58+
expect(getOutput()).toContain("blah/docker");
59+
await stop?.();
60+
});
61+
}
62+
);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "container-app",
3+
"main": "src/index.ts",
4+
"compatibility_date": "2025-04-03",
5+
"dev": { "enable_containers": false },
6+
"containers": [
7+
{
8+
"configuration": {
9+
"image": "./Dockerfile",
10+
},
11+
"class_name": "Container",
12+
"name": "http2",
13+
"max_instances": 2,
14+
},
15+
],
16+
"durable_objects": {
17+
"bindings": [
18+
{
19+
"class_name": "Container",
20+
"name": "CONTAINER",
21+
},
22+
],
23+
},
24+
"migrations": [
25+
{
26+
"tag": "v1",
27+
"new_classes": ["Container"],
28+
},
29+
],
30+
}

fixtures/container-app/wrangler.jsonc

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@
22
"name": "container-app",
33
"main": "src/index.ts",
44
"compatibility_date": "2025-04-03",
5-
"migrations": [
6-
{
7-
"new_sqlite_classes": ["Container"],
8-
"tag": "v1",
9-
},
10-
],
115
"containers": [
126
{
137
"configuration": {
@@ -26,7 +20,10 @@
2620
},
2721
],
2822
},
29-
"observability": {
30-
"enabled": true,
31-
},
23+
"migrations": [
24+
{
25+
"tag": "v1",
26+
"new_classes": ["Container"],
27+
},
28+
],
3229
}

fixtures/container-app/wrangler.no-containers.jsonc

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,6 @@
33
"name": "no-container-app",
44
"main": "src/index.ts",
55
"compatibility_date": "2025-04-03",
6-
"migrations": [
7-
{
8-
"new_sqlite_classes": ["Container"],
9-
"tag": "v1",
10-
},
11-
],
12-
136
"durable_objects": {
147
"bindings": [
158
{
@@ -18,7 +11,10 @@
1811
},
1912
],
2013
},
21-
"observability": {
22-
"enabled": true,
23-
},
14+
"migrations": [
15+
{
16+
"tag": "v1",
17+
"new_classes": ["Container"],
18+
},
19+
],
2420
}

packages/miniflare/src/index.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ import {
3838
Response,
3939
} from "./http";
4040
import {
41+
ContainerController,
4142
ContainerOptions,
42-
ContainerService,
4343
D1_PLUGIN_NAME,
4444
DURABLE_OBJECTS_PLUGIN_NAME,
4545
DurableObjectClassNames,
@@ -903,7 +903,7 @@ export class Miniflare {
903903
#socketPorts?: SocketPorts;
904904
#runtimeDispatcher?: Dispatcher;
905905
#proxyClient?: ProxyClient;
906-
#containerService?: ContainerService;
906+
#containerController?: ContainerController;
907907

908908
#cfObject?: Record<string, any> = {};
909909

@@ -1589,7 +1589,9 @@ export class Miniflare {
15891589
innerBindings: Worker_Binding[];
15901590
}[] = [];
15911591

1592-
let containerOptions: NonNullable<ContainerOptions> = {};
1592+
let containerOptions: {
1593+
[className: string]: ContainerOptions;
1594+
} = {};
15931595

15941596
for (const [key, plugin] of PLUGIN_ENTRIES) {
15951597
const pluginExtensions = await plugin.getExtensions?.({
@@ -1889,12 +1891,18 @@ export class Miniflare {
18891891

18901892
if (
18911893
Object.keys(containerOptions).length &&
1892-
!sharedOpts.containers.ignore_containers
1894+
sharedOpts.containers.enableContainers
18931895
) {
1894-
if (this.#containerService === undefined) {
1895-
this.#containerService = new ContainerService(containerOptions);
1896+
if (this.#containerController === undefined) {
1897+
this.#containerController = new ContainerController(
1898+
containerOptions,
1899+
this.#sharedOpts.containers
1900+
);
18961901
} else {
1897-
this.#containerService.updateConfig(containerOptions);
1902+
this.#containerController.updateConfig(
1903+
containerOptions,
1904+
this.#sharedOpts.containers
1905+
);
18981906
}
18991907
}
19001908

packages/miniflare/src/plugins/containers/index.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,35 @@ import { z } from "zod";
22
import { Plugin } from "../shared";
33

44
const className = z.string();
5+
6+
export const ContainerSchema = z.object({
7+
image: z.string(),
8+
maxInstances: z.number().optional(),
9+
imageBuildContext: z.string().optional(),
10+
args: z.record(z.string(), z.string()).default({}),
11+
exposedPorts: z.number().array().optional(),
12+
});
513
export const ContainersOptionsSchema = z.object({
6-
containers: z
7-
.record(
8-
className,
9-
z.object({
10-
image: z.string(),
11-
maxInstances: z.number().optional(),
12-
imageBuildContext: z.string().optional(),
13-
exposedPorts: z.number().array().optional(),
14-
})
15-
)
16-
.optional(),
14+
containers: z.record(className, ContainerSchema).optional(),
1715
});
1816

19-
export type ContainerOptions = z.infer<typeof ContainersOptionsSchema>;
17+
export type ContainerOptions = z.infer<typeof ContainerSchema>;
2018

21-
// TODO: Doesn't exist in wrangler yet
22-
export const ContainersSharedOptions = z.object({
23-
ignore_containers: z.string().optional(),
19+
export const ContainersSharedSchema = z.object({
20+
enableContainers: z.boolean().default(true),
21+
dockerPath: z.string().default("docker"),
2422
});
2523

24+
export type ContainersSharedOptions = z.infer<typeof ContainersSharedSchema>;
25+
2626
export const CONTAINER_PLUGIN_NAME = "containers";
2727

2828
export const CONTAINER_PLUGIN: Plugin<
2929
typeof ContainersOptionsSchema,
30-
typeof ContainersSharedOptions
30+
typeof ContainersSharedSchema
3131
> = {
3232
options: ContainersOptionsSchema,
33-
sharedOptions: ContainersSharedOptions,
33+
sharedOptions: ContainersSharedSchema,
3434
async getBindings() {
3535
return;
3636
},
Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,39 @@
1-
import { ContainerOptions } from "../index";
1+
import { ContainerOptions, ContainersSharedOptions } from "../index";
22

3-
export class ContainerService {
4-
#containerOptions: ContainerOptions;
5-
constructor(containerOptions: ContainerOptions) {
3+
/**
4+
* ContainerController manages container configuration, building or pulling
5+
* containers, and cleaning up containers at the end of the dev session.
6+
*/
7+
export class ContainerController {
8+
#containerOptions: { [className: string]: ContainerOptions };
9+
#sharedOptions: ContainersSharedOptions;
10+
constructor(
11+
containerOptions: { [className: string]: ContainerOptions },
12+
sharedOptions: ContainersSharedOptions
13+
) {
614
this.#containerOptions = containerOptions;
15+
this.#sharedOptions = sharedOptions;
716
this.help();
817
}
918

10-
updateConfig(containerOptions: ContainerOptions): void {
19+
updateConfig(
20+
containerOptions: {
21+
[className: string]: ContainerOptions;
22+
},
23+
sharedOptions: ContainersSharedOptions
24+
): void {
1125
this.#containerOptions = containerOptions;
26+
this.#sharedOptions = sharedOptions;
1227
this.help();
1328
}
1429

1530
help() {
16-
console.log("Hello from ContainerService!");
31+
console.log("Hello from ContainerController!");
1732
console.log(
1833
`Container Options: ${JSON.stringify(this.#containerOptions, null, 2)}`
1934
);
35+
console.log(
36+
`Shared Options: ${JSON.stringify(this.#sharedOptions, null, 2)}`
37+
);
2038
}
2139
}

packages/miniflare/src/plugins/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,4 @@ export * from "./dispatch-namespace";
182182
export * from "./images";
183183
export * from "./vectorize";
184184
export * from "./containers";
185-
export { ContainerService } from "./containers/service";
185+
export { ContainerController } from "./containers/service";

packages/wrangler/src/__tests__/config-validation-pages.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ describe("validatePagesConfig()", () => {
180180
local_protocol: "https",
181181
upstream_protocol: "https",
182182
host: "test-host",
183+
enable_containers: false,
183184
},
184185
},
185186
};

0 commit comments

Comments
 (0)