Skip to content

Commit e10c3e2

Browse files
Improve containers exposed port validation logic (#9879)
* update containers error message presented when no port is exported by the image * fix: enable Dockerfile exposed port validation on linux as well * update e2e
1 parent 1f3050b commit e10c3e2

File tree

5 files changed

+72
-42
lines changed

5 files changed

+72
-42
lines changed

.changeset/rotten-peaches-end.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudflare/containers-shared": patch
3+
---
4+
5+
fix: enable Dockerfile exposed port validation on linux as well

.changeset/shiny-snails-bake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudflare/containers-shared": patch
3+
---
4+
5+
update error message presented when no port is exported by the image

packages/containers-shared/src/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ const getContainerIdsFromImage = async (
166166

167167
/**
168168
* While all ports are exposed in prod, a limitation of local dev with docker is that
169-
* non-linux users will have to manually expose ports in their Dockerfile.
169+
* users will have to manually expose ports in their Dockerfile.
170170
* We want to fail early and clearly if a user tries to develop with a container
171171
* that has no ports exposed and is definitely not accessible.
172172
*
@@ -180,10 +180,10 @@ export async function checkExposedPorts(
180180
imageTag: options.imageTag,
181181
formatString: "{{ len .Config.ExposedPorts }}",
182182
});
183-
if (output === "0" && process.platform !== "linux") {
183+
if (output === "0") {
184184
throw new Error(
185-
`The container "${options.class_name}" does not expose any ports.\n` +
186-
"To develop containers locally on non-Linux platforms, you must expose any ports that you call with `getTCPPort()` in your Dockerfile."
185+
`The container "${options.class_name}" does not expose any ports. In your Dockerfile, please expose any ports you intend to connect to.\n` +
186+
"For additional information please see: https://developers.cloudflare.com/containers/local-dev/#exposing-ports.\n"
187187
);
188188
}
189189
}

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

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { mkdirSync, rmSync, writeFileSync } from "fs";
2-
import { isDockerfile } from "./../src/utils";
1+
import { mkdirSync, writeFileSync } from "fs";
2+
import { checkExposedPorts, isDockerfile } from "./../src/utils";
33
import { runInTempDir } from "./helpers/run-in-tmp-dir";
44

55
describe("isDockerfile", () => {
@@ -60,3 +60,45 @@ describe("isDockerfile", () => {
6060
`);
6161
});
6262
});
63+
64+
let docketImageInspectResult = "0";
65+
66+
vi.mock("../src/inspect", async (importOriginal) => {
67+
const mod: object = await importOriginal();
68+
return {
69+
...mod,
70+
dockerImageInspect: () => docketImageInspectResult,
71+
};
72+
});
73+
74+
describe("checkExposedPorts", () => {
75+
beforeEach(() => {
76+
docketImageInspectResult = "1";
77+
});
78+
79+
it("should not error when some ports are exported", async () => {
80+
docketImageInspectResult = "1";
81+
await expect(
82+
checkExposedPorts("./container-context/Dockerfile", {
83+
image: "",
84+
imageTag: "",
85+
class_name: "MyContainer",
86+
})
87+
).resolves.toBeUndefined();
88+
});
89+
90+
it("should error, with an appropriate message when no ports are exported", async () => {
91+
docketImageInspectResult = "0";
92+
expect(
93+
checkExposedPorts("./container-context/Dockerfile", {
94+
image: "",
95+
imageTag: "",
96+
class_name: "MyContainer",
97+
})
98+
).rejects.toThrowErrorMatchingInlineSnapshot(`
99+
[Error: The container "MyContainer" does not expose any ports. In your Dockerfile, please expose any ports you intend to connect to.
100+
For additional information please see: https://developers.cloudflare.com/containers/local-dev/#exposing-ports.
101+
]
102+
`);
103+
});
104+
});

packages/wrangler/e2e/containers.dev.test.ts

Lines changed: 14 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -263,43 +263,21 @@ describe
263263
);
264264
});
265265

266-
describe("make sure ports are exposed if necessary", () => {
267-
beforeEach(async () => {
268-
await helper.seed({
269-
Dockerfile: dedent`
270-
FROM alpine:latest
271-
CMD ["echo", "hello world"]
272-
`,
273-
});
274-
if (source === "pull") {
275-
await helper.run(
276-
`wrangler containers build . -t ${workerName}:tmp-e2e -p`
277-
);
278-
}
266+
it("errors if no ports are exposed", async () => {
267+
await helper.seed({
268+
Dockerfile: dedent`
269+
FROM alpine:latest
270+
CMD ["echo", "hello world"]
271+
`,
279272
});
280-
// this will never run in CI
281-
it.skipIf(process.platform === "linux")(
282-
"errors in windows/macos if no ports are exposed",
283-
async () => {
284-
const worker = helper.runLongLived("wrangler dev");
285-
expect(await worker.exitCode).toBe(1);
286-
expect(await worker.output).toContain("does not expose any ports");
287-
}
288-
);
289-
290-
it.skipIf(process.platform !== "linux")(
291-
"doesn't error in linux if no ports are exposed",
292-
async () => {
293-
const worker = helper.runLongLived("wrangler dev");
294-
await worker.readUntil(/Preparing container/);
295-
if (source === "pull") {
296-
await worker.readUntil(/Status/);
297-
} else {
298-
await worker.readUntil(/DONE/);
299-
}
300-
await worker.readUntil(/Container image\(s\) ready/);
301-
}
302-
);
273+
if (source === "pull") {
274+
await helper.run(
275+
`wrangler containers build . -t ${workerName}:tmp-e2e -p`
276+
);
277+
}
278+
const worker = helper.runLongLived("wrangler dev");
279+
expect(await worker.exitCode).toBe(1);
280+
expect(await worker.output).toContain("does not expose any ports");
303281
});
304282

305283
it("errors if docker is not installed", async () => {

0 commit comments

Comments
 (0)