From 87aca0081d6542525c4fb67a26db5aa4532e43cb Mon Sep 17 00:00:00 2001 From: Greg Anders Date: Tue, 1 Jul 2025 12:21:18 -0500 Subject: [PATCH 1/2] Support image name without account ID If a user uses an image in the Cloudflare managed registry they can specify the image name without including their full account ID. For example: image = "registry.cloudflare.com/hello/world:1.0" will now "just work", as Wrangler will automatically include their account ID in the appropriate location when creating the application. This matches the new behavior in the Dash, where we also omit the account ID in the image name. --- .changeset/fruity-pillows-peel.md | 5 + .../src/__tests__/cloudchamber/apply.test.ts | 131 +++++++++++------- .../src/__tests__/cloudchamber/build.test.ts | 26 ++-- .../src/__tests__/cloudchamber/utils.ts | 8 +- .../wrangler/src/__tests__/deploy.test.ts | 16 +-- packages/wrangler/src/cloudchamber/apply.ts | 50 ++++++- 6 files changed, 158 insertions(+), 78 deletions(-) create mode 100644 .changeset/fruity-pillows-peel.md diff --git a/.changeset/fruity-pillows-peel.md b/.changeset/fruity-pillows-peel.md new file mode 100644 index 000000000000..ee7376393d12 --- /dev/null +++ b/.changeset/fruity-pillows-peel.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +Support container image names without account ID diff --git a/packages/wrangler/src/__tests__/cloudchamber/apply.test.ts b/packages/wrangler/src/__tests__/cloudchamber/apply.test.ts index 61fead5ebd5a..2f84e99e66eb 100644 --- a/packages/wrangler/src/__tests__/cloudchamber/apply.test.ts +++ b/packages/wrangler/src/__tests__/cloudchamber/apply.test.ts @@ -1,4 +1,5 @@ import { + getCloudflareContainerRegistry, SchedulingPolicy, SecretAccessType, } from "@cloudflare/containers-shared"; @@ -30,14 +31,20 @@ function mockGetApplications(applications: Application[]) { ); } -function mockCreateApplication(expected?: Application) { +function mockCreateApplication( + response?: Partial, + expected?: Partial +) { msw.use( http.post( "*/applications", async ({ request }) => { - const body = await request.json(); + const body = (await request.json()) as CreateApplicationRequest; + if (expected !== undefined) { + expect(body).toMatchObject(expected); + } expect(body).toHaveProperty("instances"); - return HttpResponse.json(expected); + return HttpResponse.json(response); }, { once: true } ) @@ -73,6 +80,12 @@ function mockModifyApplication( } describe("cloudchamber apply", () => { + /* eslint no-irregular-whitespace: ["error", { "skipTemplates": true }] + --- + Wrangler emits \u200a instead of "regular" whitespace in some cases. eslint doesn't like + this so we disable the warning when mixed whitespace is used in template strings. + */ + const { setIsTTY } = useMockIsTTY(); const std = mockCLIOutput(); @@ -102,9 +115,8 @@ describe("cloudchamber apply", () => { ], }); mockGetApplications([]); - mockCreateApplication({ id: "abc" } as Application); + mockCreateApplication({ id: "abc" }); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stderr).toMatchInlineSnapshot(`""`); expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application @@ -132,7 +144,6 @@ describe("cloudchamber apply", () => { " `); - /* eslint-enable */ }); test("can apply a simple existing application", async () => { @@ -177,7 +188,6 @@ describe("cloudchamber apply", () => { ]); const applicationReqBodyPromise = mockModifyApplication(); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -205,7 +215,6 @@ describe("cloudchamber apply", () => { const app = await applicationReqBodyPromise; expect(app.constraints?.tier).toEqual(2); expect(app.instances).toEqual(4); - /* eslint-enable */ }); test("can apply a simple existing application and create other (max_instances)", async () => { @@ -253,11 +262,10 @@ describe("cloudchamber apply", () => { }, ]); const res = mockModifyApplication(); - mockCreateApplication({ id: "abc" } as Application); + mockCreateApplication({ id: "abc" }); await runWrangler("cloudchamber apply"); const body = await res; expect(body).not.toHaveProperty("instances"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -296,7 +304,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("can skip a simple existing application and create other", async () => { @@ -343,10 +350,9 @@ describe("cloudchamber apply", () => { }, }, ]); - mockCreateApplication({ id: "abc" } as Application); + mockCreateApplication({ id: "abc" }); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -382,7 +388,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("can apply a simple existing application and create other", async () => { @@ -429,10 +434,9 @@ describe("cloudchamber apply", () => { }, ]); const res = mockModifyApplication(); - mockCreateApplication({ id: "abc" } as Application); + mockCreateApplication({ id: "abc" }); await runWrangler("cloudchamber apply"); await res; - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -470,7 +474,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("can apply a simple existing application (labels)", async () => { @@ -568,7 +571,6 @@ describe("cloudchamber apply", () => { const res = mockModifyApplication(); await runWrangler("cloudchamber apply"); await res; - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -604,7 +606,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("can apply an application, and there is no changes (retrocompatibility with regional scheduling policy)", async () => { @@ -702,7 +703,6 @@ describe("cloudchamber apply", () => { }, ]); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -715,7 +715,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("can apply an application, and there is no changes (two applications)", async () => { @@ -816,7 +815,6 @@ describe("cloudchamber apply", () => { { ...completeApp, version: 1, name: "my-container-app-2", id: "abc2" }, ]); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -831,7 +829,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("can apply an application, and there is no changes", async () => { @@ -929,7 +926,6 @@ describe("cloudchamber apply", () => { }, ]); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -942,7 +938,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("can apply an application, and there is no changes (two applications)", async () => { @@ -1043,7 +1038,6 @@ describe("cloudchamber apply", () => { { ...completeApp, version: 1, name: "my-container-app-2", id: "abc2" }, ]); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1058,7 +1052,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("can enable observability logs (top-level field)", async () => { @@ -1101,7 +1094,6 @@ describe("cloudchamber apply", () => { ]); const applicationReqBodyPromise = mockModifyApplication(); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1130,7 +1122,6 @@ describe("cloudchamber apply", () => { const app = await applicationReqBodyPromise; expect(app.constraints?.tier).toEqual(1); expect(app.instances).toEqual(1); - /* eslint-enable */ }); test("can enable observability logs (logs field)", async () => { @@ -1173,7 +1164,6 @@ describe("cloudchamber apply", () => { ]); const applicationReqBodyPromise = mockModifyApplication(); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1202,7 +1192,6 @@ describe("cloudchamber apply", () => { const app = await applicationReqBodyPromise; expect(app.constraints?.tier).toEqual(1); expect(app.instances).toEqual(1); - /* eslint-enable */ }); test("can disable observability logs (top-level field)", async () => { @@ -1250,7 +1239,6 @@ describe("cloudchamber apply", () => { ]); const applicationReqBodyPromise = mockModifyApplication(); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1276,7 +1264,6 @@ describe("cloudchamber apply", () => { const app = await applicationReqBodyPromise; expect(app.constraints?.tier).toEqual(1); expect(app.instances).toEqual(1); - /* eslint-enable */ }); test("can disable observability logs (logs field)", async () => { @@ -1324,7 +1311,6 @@ describe("cloudchamber apply", () => { ]); const applicationReqBodyPromise = mockModifyApplication(); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1350,7 +1336,6 @@ describe("cloudchamber apply", () => { const app = await applicationReqBodyPromise; expect(app.constraints?.tier).toEqual(1); expect(app.instances).toEqual(1); - /* eslint-enable */ }); test("can disable observability logs (absent field)", async () => { @@ -1397,7 +1382,6 @@ describe("cloudchamber apply", () => { ]); const applicationReqBodyPromise = mockModifyApplication(); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1423,7 +1407,6 @@ describe("cloudchamber apply", () => { const app = await applicationReqBodyPromise; expect(app.constraints?.tier).toEqual(1); expect(app.instances).toEqual(1); - /* eslint-enable */ }); test("ignores deprecated observability.logging", async () => { @@ -1473,7 +1456,6 @@ describe("cloudchamber apply", () => { ]); const applicationReqBodyPromise = mockModifyApplication(); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1499,7 +1481,6 @@ describe("cloudchamber apply", () => { const app = await applicationReqBodyPromise; expect(app.constraints?.tier).toEqual(1); expect(app.instances).toEqual(1); - /* eslint-enable */ }); test("keeps observability logs enabled", async () => { @@ -1549,7 +1530,6 @@ describe("cloudchamber apply", () => { }, ]); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1562,7 +1542,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("keeps observability logs disabled (undefined in the app)", async () => { @@ -1603,7 +1582,6 @@ describe("cloudchamber apply", () => { }, ]); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1616,7 +1594,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("keeps observability logs disabled (false in the app)", async () => { @@ -1665,7 +1642,6 @@ describe("cloudchamber apply", () => { }, ]); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1678,7 +1654,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("can apply a simple application (instance type)", async () => { @@ -1699,9 +1674,8 @@ describe("cloudchamber apply", () => { ], }); mockGetApplications([]); - mockCreateApplication({ id: "abc" } as Application); + mockCreateApplication({ id: "abc" }); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1729,7 +1703,6 @@ describe("cloudchamber apply", () => { " `); expect(std.stderr).toMatchInlineSnapshot(`""`); - /* eslint-enable */ }); test("can apply a simple existing application (instance type)", async () => { @@ -1775,7 +1748,6 @@ describe("cloudchamber apply", () => { ]); const applicationReqBodyPromise = mockModifyApplication(); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1808,7 +1780,6 @@ describe("cloudchamber apply", () => { expect(std.stderr).toMatchInlineSnapshot(`""`); const app = await applicationReqBodyPromise; expect(app.configuration?.instance_type).toEqual("standard"); - /* eslint-enable */ }); test("falls back on dev instance type when instance type is absent", async () => { @@ -1853,7 +1824,6 @@ describe("cloudchamber apply", () => { ]); const applicationReqBodyPromise = mockModifyApplication(); await runWrangler("cloudchamber apply"); - /* eslint-disable */ expect(std.stdout).toMatchInlineSnapshot(` "╭ Deploy a container application deploy changes to your application │ @@ -1886,6 +1856,63 @@ describe("cloudchamber apply", () => { expect(std.stderr).toMatchInlineSnapshot(`""`); const app = await applicationReqBodyPromise; expect(app.configuration?.instance_type).toEqual("dev"); - /* eslint-enable */ + }); + + test("expands image names from managed registry", async () => { + setIsTTY(false); + const registry = getCloudflareContainerRegistry(); + writeWranglerConfig({ + name: "my-container", + containers: [ + { + name: "my-container-app", + instances: 3, + class_name: "DurableObjectClass", + image: `${registry}/hello:1.0`, + constraints: { + tier: 2, + }, + }, + ], + }); + + mockGetApplications([]); + mockCreateApplication( + { id: "abc" }, + { + configuration: { + image: `${registry}/some-account-id/hello:1.0`, + }, + } + ); + + await runWrangler("cloudchamber apply"); + expect(std.stderr).toMatchInlineSnapshot(`""`); + expect(std.stdout).toMatchInlineSnapshot(` + "╭ Deploy a container application deploy changes to your application + │ + │ Container application changes + │ + ├ NEW my-container-app + │ + │ [[containers]] + │ name = \\"my-container-app\\" + │ instances = 3 + │ scheduling_policy = \\"default\\" + │ + │ [containers.constraints] + │ tier = 2 + │ + │ [containers.configuration] + │ image = \\"${registry}/hello:1.0\\" + │ instance_type = \\"dev\\" + │ + │ + │  SUCCESS  Created application my-container-app (Application ID: abc) + │ + ╰ Applied changes + + " + `); }); }); diff --git a/packages/wrangler/src/__tests__/cloudchamber/build.test.ts b/packages/wrangler/src/__tests__/cloudchamber/build.test.ts index 63c00841cf21..76ea7c25e692 100644 --- a/packages/wrangler/src/__tests__/cloudchamber/build.test.ts +++ b/packages/wrangler/src/__tests__/cloudchamber/build.test.ts @@ -62,7 +62,7 @@ describe("buildAndMaybePush", () => { buildCmd: [ "build", "-t", - `${getCloudflareContainerRegistry()}/test_account_id/test-app:tag`, + `${getCloudflareContainerRegistry()}/some-account-id/test-app:tag`, "--platform", "linux/amd64", "--provenance=false", @@ -73,13 +73,13 @@ describe("buildAndMaybePush", () => { dockerfile, }); expect(dockerImageInspect).toHaveBeenCalledWith("/custom/docker/path", { - imageTag: `${getCloudflareContainerRegistry()}/test_account_id/test-app:tag`, + imageTag: `${getCloudflareContainerRegistry()}/some-account-id/test-app:tag`, formatString: "{{ .Size }} {{ len .RootFS.Layers }} {{json .RepoDigests}}", }); expect(runDockerCmd).toHaveBeenCalledWith("/custom/docker/path", [ "push", - `${getCloudflareContainerRegistry()}/test_account_id/test-app:tag`, + `${getCloudflareContainerRegistry()}/some-account-id/test-app:tag`, ]); expect(dockerLoginManagedRegistry).toHaveBeenCalledWith( "/custom/docker/path" @@ -94,7 +94,7 @@ describe("buildAndMaybePush", () => { buildCmd: [ "build", "-t", - `${getCloudflareContainerRegistry()}/test_account_id/test-app:tag`, + `${getCloudflareContainerRegistry()}/some-account-id/test-app:tag`, "--platform", "linux/amd64", "--provenance=false", @@ -107,11 +107,11 @@ describe("buildAndMaybePush", () => { expect(runDockerCmd).toHaveBeenCalledTimes(1); expect(runDockerCmd).toHaveBeenCalledWith("docker", [ "push", - `${getCloudflareContainerRegistry()}/test_account_id/test-app:tag`, + `${getCloudflareContainerRegistry()}/some-account-id/test-app:tag`, ]); expect(dockerImageInspect).toHaveBeenCalledOnce(); expect(dockerImageInspect).toHaveBeenCalledWith("docker", { - imageTag: `${getCloudflareContainerRegistry()}/test_account_id/test-app:tag`, + imageTag: `${getCloudflareContainerRegistry()}/some-account-id/test-app:tag`, formatString: "{{ .Size }} {{ len .RootFS.Layers }} {{json .RepoDigests}}", }); @@ -121,7 +121,7 @@ describe("buildAndMaybePush", () => { it("should be able to build image and not push if it already exists in remote", async () => { vi.mocked(runDockerCmd).mockResolvedValueOnce(); vi.mocked(dockerImageInspect).mockResolvedValue( - '53387881 2 ["registry.cloudflare.com/test_account_id/test-app@sha256:three"]' + '53387881 2 ["registry.cloudflare.com/some-account-id/test-app@sha256:three"]' ); await runWrangler( "containers build ./container-context -t test-app:tag -p" @@ -130,7 +130,7 @@ describe("buildAndMaybePush", () => { buildCmd: [ "build", "-t", - `${getCloudflareContainerRegistry()}/test_account_id/test-app:tag`, + `${getCloudflareContainerRegistry()}/some-account-id/test-app:tag`, "--platform", "linux/amd64", "--provenance=false", @@ -147,18 +147,18 @@ describe("buildAndMaybePush", () => { [ "manifest", "inspect", - `${getCloudflareContainerRegistry()}/test_account_id/test-app@sha256:three`, + `${getCloudflareContainerRegistry()}/some-account-id/test-app@sha256:three`, ], "ignore" ); expect(runDockerCmd).toHaveBeenNthCalledWith(2, "docker", [ "image", "rm", - `${getCloudflareContainerRegistry()}/test_account_id/test-app:tag`, + `${getCloudflareContainerRegistry()}/some-account-id/test-app:tag`, ]); expect(dockerImageInspect).toHaveBeenCalledOnce(); expect(dockerImageInspect).toHaveBeenCalledWith("docker", { - imageTag: `${getCloudflareContainerRegistry()}/test_account_id/test-app:tag`, + imageTag: `${getCloudflareContainerRegistry()}/some-account-id/test-app:tag`, formatString: "{{ .Size }} {{ len .RootFS.Layers }} {{json .RepoDigests}}", }); @@ -172,7 +172,7 @@ describe("buildAndMaybePush", () => { buildCmd: [ "build", "-t", - `${getCloudflareContainerRegistry()}/test_account_id/test-app`, + `${getCloudflareContainerRegistry()}/some-account-id/test-app`, "--platform", "linux/amd64", "--provenance=false", @@ -194,7 +194,7 @@ describe("buildAndMaybePush", () => { buildCmd: [ "build", "-t", - `${getCloudflareContainerRegistry()}/test_account_id/test-app`, + `${getCloudflareContainerRegistry()}/some-account-id/test-app`, "--platform", "linux/amd64", "--provenance=false", diff --git a/packages/wrangler/src/__tests__/cloudchamber/utils.ts b/packages/wrangler/src/__tests__/cloudchamber/utils.ts index 374d26f8bbce..14ed3efec1a2 100644 --- a/packages/wrangler/src/__tests__/cloudchamber/utils.ts +++ b/packages/wrangler/src/__tests__/cloudchamber/utils.ts @@ -21,12 +21,14 @@ export function mockAccount() { const spy = vi.spyOn(user, "getScopes"); spy.mockImplementationOnce(() => ["cloudchamber:write", "containers:write"]); + const accountId = process.env.CLOUDFLARE_ACCOUNT_ID; + msw.use( http.get( "*/me", async () => { return HttpResponse.json({ - external_account_id: "test_account_id", + external_account_id: accountId, limits: { disk_mb_per_deployment: 2000, }, @@ -41,6 +43,8 @@ export function mockAccountV4(scopes: user.Scope[] = ["containers:write"]) { const spy = vi.spyOn(user, "getScopes"); spy.mockImplementationOnce(() => scopes); + const accountId = process.env.CLOUDFLARE_ACCOUNT_ID; + msw.use( http.get( "*/me", @@ -49,7 +53,7 @@ export function mockAccountV4(scopes: user.Scope[] = ["containers:write"]) { { success: true, result: { - external_account_id: "test_account_id", + external_account_id: accountId, limits: { disk_mb_per_deployment: 2000, }, diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index d2c6fab906e5..fb4d7c70bf69 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -8784,7 +8784,7 @@ addEventListener('fetch', event => {});` "build", "-t", getCloudflareContainerRegistry() + - "/test_account_id/my-container:Galaxy", + "/some-account-id/my-container:Galaxy", "--platform", "linux/amd64", "--provenance=false", @@ -8823,7 +8823,7 @@ addEventListener('fetch', event => {});` expect(args).toEqual([ "image", "inspect", - `${getCloudflareContainerRegistry()}/test_account_id/my-container:Galaxy`, + `${getCloudflareContainerRegistry()}/some-account-id/my-container:Galaxy`, "--format", "{{ .Size }} {{ len .RootFS.Layers }} {{json .RepoDigests}}", ]); @@ -8847,7 +8847,7 @@ addEventListener('fetch', event => {});` setImmediate(() => { stdout.emit( "data", - `123456 4 ["${getCloudflareContainerRegistry()}/test_account_id/my-container@sha256:three"]` + `123456 4 ["${getCloudflareContainerRegistry()}/some-account-id/my-container@sha256:three"]` ); }); @@ -8888,7 +8888,7 @@ addEventListener('fetch', event => {});` expect(args).toEqual([ "manifest", "inspect", - `${getCloudflareContainerRegistry()}/test_account_id/my-container@three`, + `${getCloudflareContainerRegistry()}/some-account-id/my-container@three`, ]); const readable = new Writable({ write() {}, @@ -8917,7 +8917,7 @@ addEventListener('fetch', event => {});` expect(cmd).toBe("/usr/bin/docker"); expect(args).toEqual([ "push", - `${getCloudflareContainerRegistry()}/test_account_id/my-container:Galaxy`, + `${getCloudflareContainerRegistry()}/some-account-id/my-container:Galaxy`, ]); return defaultChildProcess(); }); @@ -8977,7 +8977,7 @@ addEventListener('fetch', event => {});` return HttpResponse.json({ success: true, result: { - account_id: "test_account_id", + account_id: "some-account-id", registry_host: getCloudflareContainerRegistry(), username: "v1", password: "mockpassword", @@ -8998,7 +8998,7 @@ addEventListener('fetch', event => {});` configuration: { image: getCloudflareContainerRegistry() + - "/test_account_id/my-container:Galaxy", + "/some-account-id/my-container:Galaxy", }, }); @@ -9035,7 +9035,7 @@ addEventListener('fetch', event => {});` Uploaded test-name (TIMINGS) Building image my-container:Galaxy - Image does not exist remotely, pushing: registry.cloudflare.com/test_account_id/my-container:Galaxy + Image does not exist remotely, pushing: registry.cloudflare.com/some-account-id/my-container:Galaxy Deployed test-name triggers (TIMINGS) https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" diff --git a/packages/wrangler/src/cloudchamber/apply.ts b/packages/wrangler/src/cloudchamber/apply.ts index 81ea00077803..0ff1804eedd0 100644 --- a/packages/wrangler/src/cloudchamber/apply.ts +++ b/packages/wrangler/src/cloudchamber/apply.ts @@ -13,12 +13,14 @@ import { ApplicationsService, CreateApplicationRolloutRequest, DeploymentMutationError, + getCloudflareContainerRegistry, InstanceType, RolloutsService, SchedulingPolicy, } from "@cloudflare/containers-shared"; import { formatConfigSnippet } from "../config"; import { FatalError, UserError } from "../errors"; +import { getAccountId } from "../user"; import { cleanForInstanceType, promiseSpinner } from "./common"; import { diffLines } from "./helpers/diff"; import type { Config } from "../config"; @@ -415,6 +417,33 @@ function sortObjectRecursive>( return sortObjectKeys(objectCopy) as T; } +// Resolve an image name to the full unambiguous name. +// +// For now, this only converts images stored in the managed registry to contain +// the user's account ID in the path. +async function resolveImageName( + config: Config, + image: string +): Promise { + let url: URL; + try { + url = new URL(`http://${image}`); + } catch (_) { + return image; + } + + if (url.hostname !== getCloudflareContainerRegistry()) { + return image; + } + + const accountId = config.account_id || (await getAccountId(config)); + if (url.pathname.startsWith(`/${accountId}`)) { + return image; + } + + return `${url.hostname}/${accountId}${url.pathname}`; +} + export async function apply( args: { skipDefaults: boolean | undefined; @@ -686,7 +715,17 @@ export async function apply( printLine(el, " "); }); - const configToPush = { ...appConfig }; + const configToPush = { + ...appConfig, + + configuration: { + ...appConfig.configuration, + + // De-sugar image name. We do it here so that the user + // sees the simplified image name in diffs. + image: await resolveImageName(config, appConfig.configuration.image), + }, + }; // add to the actions array to create the app later actions.push({ @@ -716,7 +755,11 @@ export async function apply( return message; } - return ` ${err.body.error}`; + if (err.body.error !== undefined) { + return ` ${err.body.error}`; + } + + return JSON.stringify(err.body); } for (const action of actions) { @@ -769,7 +812,8 @@ export async function apply( action.application.max_instances !== undefined ? undefined : action.application.instances, - }) + }), + { message: `Modifying ${action.application.name}` } ); } catch (err) { if (!(err instanceof Error)) { From a65d3bcf61e9471d3ccbe158c65dadbe987ae92e Mon Sep 17 00:00:00 2001 From: Greg Anders Date: Thu, 3 Jul 2025 11:55:22 -0500 Subject: [PATCH 2/2] Skip containers E2E tests when no auth available --- packages/wrangler/e2e/containers.dev.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/wrangler/e2e/containers.dev.test.ts b/packages/wrangler/e2e/containers.dev.test.ts index 93f65f184011..b5b1fc4e72ca 100644 --- a/packages/wrangler/e2e/containers.dev.test.ts +++ b/packages/wrangler/e2e/containers.dev.test.ts @@ -19,7 +19,10 @@ const imageSource = ["pull", "build"]; // We can only really run these tests on Linux, because we build our images for linux/amd64, // and github runners don't really support container virtualization in any sane way describe - .skipIf(process.platform !== "linux" && process.env.CI === "true") + .skipIf( + !CLOUDFLARE_ACCOUNT_ID || + (process.platform !== "linux" && process.env.CI === "true") + ) .each(imageSource)( "containers local dev tests: %s", { timeout: 90_000 },