Skip to content

Commit b0217f9

Browse files
Require container images use a supported platform (#9996)
* CC-5610: Disallow users from pushing container images with an unsupported platform The runtime only runs on amd64 right now. Require that images pushed to the managed registry are configured for a linux/amd64 platform to avoid runtime errors.
1 parent 89cb004 commit b0217f9

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

.changeset/old-clubs-swim.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Disallow users from pushing images with unsupported platforms to the container image registry

packages/wrangler/src/__tests__/cloudchamber/build.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,34 @@ describe("buildAndMaybePush", () => {
223223
});
224224
});
225225

226+
it("should be able to build image with platform specified", async () => {
227+
await runWrangler(
228+
"containers build ./container-context -t test-app:tag -p --platform linux/amd64"
229+
);
230+
expect(dockerBuild).toHaveBeenCalledWith("docker", {
231+
buildCmd: [
232+
"build",
233+
"-t",
234+
`${getCloudflareContainerRegistry()}/test-app:tag`,
235+
"--platform",
236+
"linux/amd64",
237+
"--provenance=false",
238+
"-f",
239+
"-",
240+
"./container-context",
241+
],
242+
dockerfile,
243+
});
244+
});
245+
246+
it("should fail with an invalid platform", async () => {
247+
await expect(
248+
runWrangler(
249+
"containers build ./container-context -t test-app:tag -p --platform linux/arm64"
250+
)
251+
).rejects.toThrow("Unsupported platform");
252+
});
253+
226254
it("should throw UserError when docker build fails", async () => {
227255
const errorMessage = "Docker build failed";
228256
vi.mocked(dockerBuild).mockReturnValue({
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {
2+
dockerImageInspect,
3+
getCloudflareContainerRegistry,
4+
runDockerCmd,
5+
} from "@cloudflare/containers-shared";
6+
import { mockAccount, setWranglerConfig } from "../cloudchamber/utils";
7+
import { mockApiToken } from "../helpers/mock-account-id";
8+
import { mockConsoleMethods } from "../helpers/mock-console";
9+
import { useMockIsTTY } from "../helpers/mock-istty";
10+
import { runWrangler } from "../helpers/run-wrangler";
11+
12+
vi.mock("@cloudflare/containers-shared", async (importOriginal) => {
13+
const actual = await importOriginal();
14+
return Object.assign({}, actual, {
15+
dockerLoginManagedRegistry: vi.fn(),
16+
runDockerCmd: vi.fn(),
17+
dockerImageInspect: vi.fn(),
18+
});
19+
});
20+
21+
describe("containers push", () => {
22+
const std = mockConsoleMethods();
23+
const { setIsTTY } = useMockIsTTY();
24+
25+
mockApiToken();
26+
beforeEach(mockAccount);
27+
afterEach(vi.clearAllMocks);
28+
29+
it("should help", async () => {
30+
await runWrangler("containers push --help");
31+
expect(std.err).toMatchInlineSnapshot(`""`);
32+
expect(std.out).toMatchInlineSnapshot(`
33+
"wrangler containers push [TAG]
34+
35+
Push a tagged image to a Cloudflare managed registry
36+
37+
POSITIONALS
38+
TAG [string]
39+
40+
GLOBAL FLAGS
41+
-c, --config Path to Wrangler configuration file [string]
42+
--cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string]
43+
-e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string]
44+
-h, --help Show help [boolean]
45+
-v, --version Show version number [boolean]
46+
47+
OPTIONS
48+
--path-to-docker Path to your docker binary if it's not on $PATH [string] [default: \\"docker\\"]"
49+
`);
50+
});
51+
52+
it("should push image with valid platform", async () => {
53+
setIsTTY(false);
54+
setWranglerConfig({});
55+
vi.mocked(dockerImageInspect).mockResolvedValue("linux/amd64");
56+
expect(std.err).toMatchInlineSnapshot(`""`);
57+
58+
await runWrangler("containers push test-app:tag");
59+
60+
expect(runDockerCmd).toHaveBeenCalledTimes(2);
61+
expect(runDockerCmd).toHaveBeenNthCalledWith(1, "docker", [
62+
"tag",
63+
`test-app:tag`,
64+
`${getCloudflareContainerRegistry()}/some-account-id/test-app:tag`,
65+
]);
66+
expect(runDockerCmd).toHaveBeenNthCalledWith(2, "docker", [
67+
"push",
68+
`${getCloudflareContainerRegistry()}/some-account-id/test-app:tag`,
69+
]);
70+
});
71+
72+
it("should reject pushing image if platform is not linux/amd64", async () => {
73+
setIsTTY(false);
74+
setWranglerConfig({});
75+
vi.mocked(dockerImageInspect).mockResolvedValue("linux/arm64");
76+
expect(std.err).toMatchInlineSnapshot(`""`);
77+
await expect(runWrangler("containers push test-app:tag")).rejects.toThrow(
78+
"Unsupported platform"
79+
);
80+
});
81+
});

packages/wrangler/src/cloudchamber/build.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export function buildYargs(yargs: CommonYargsArgv) {
6262
describe:
6363
"Platform to build for. Defaults to the architecture support by Workers (linux/amd64)",
6464
demandOption: false,
65+
hidden: true,
66+
deprecated: true,
6567
});
6668
}
6769

@@ -225,6 +227,11 @@ export async function buildCommand(
225227
`${args.PATH} is not a directory. Please specify a valid directory path.`
226228
);
227229
}
230+
if (args.platform !== "linux/amd64") {
231+
throw new UserError(
232+
`Unsupported platform: Platform "${args.platform}" is unsupported. Please use "linux/amd64" instead.`
233+
);
234+
}
228235
// if containers are not defined, the build should still work.
229236
const containers = config.containers ?? [undefined];
230237
const pathToDockerfile = join(args.PATH, "Dockerfile");
@@ -257,6 +264,7 @@ export async function pushCommand(
257264
args.TAG
258265
);
259266
const dockerPath = args.pathToDocker ?? getDockerPath();
267+
await checkImagePlatform(dockerPath, args.TAG);
260268
await runDockerCmd(dockerPath, ["tag", args.TAG, newTag]);
261269
await runDockerCmd(dockerPath, ["push", newTag]);
262270
logger.log(`Pushed image: ${newTag}`);
@@ -269,6 +277,23 @@ export async function pushCommand(
269277
}
270278
}
271279

280+
async function checkImagePlatform(
281+
pathToDocker: string,
282+
imageTag: string,
283+
expectedPlatform: string = "linux/amd64"
284+
) {
285+
const platform = await dockerImageInspect(pathToDocker, {
286+
imageTag,
287+
formatString: "{{ .Os }}/{{ .Architecture }}",
288+
});
289+
290+
if (platform !== expectedPlatform) {
291+
throw new Error(
292+
`Unsupported platform: Image platform (${platform}) does not match the expected platform (${expectedPlatform})`
293+
);
294+
}
295+
}
296+
272297
export async function ensureDiskLimits(options: {
273298
requiredSize: number;
274299
account: CompleteAccountCustomer;

0 commit comments

Comments
 (0)