Skip to content

Commit e33d7f1

Browse files
fix: resolve 'Failed to parse URL from /me' error in container dry-run deployments
Modified buildAndMaybePush to skip account loading and disk limit validation in dry-run mode, maintaining the property that dry-run mode doesn't require API access while still allowing container building to proceed.
1 parent 28ecf85 commit e33d7f1

File tree

4 files changed

+127
-26
lines changed

4 files changed

+127
-26
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
fix: resolve "Failed to parse URL from /me" error in container dry-run deployments
6+
7+
Fixed an issue where `wrangler deploy --dry-run` with containers using local Dockerfiles would fail with "Failed to parse URL from /me" error. The fix modifies the `buildAndMaybePush` function to skip account loading and disk limit validation in dry-run mode, maintaining the property that dry-run mode doesn't require API access while still allowing container building to proceed.

packages/wrangler/src/__tests__/deploy.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9270,6 +9270,75 @@ addEventListener('fetch', event => {});`
92709270
);
92719271
});
92729272

9273+
it("should not fail with URL parsing error when using containers in dry-run mode", async () => {
9274+
writeWranglerConfig({
9275+
main: "./index.js",
9276+
durable_objects: {
9277+
bindings: [{ name: "DO", class_name: "ExampleDurableObject" }],
9278+
},
9279+
containers: [
9280+
{
9281+
name: "test-container",
9282+
class_name: "ExampleDurableObject",
9283+
image: "./Dockerfile",
9284+
},
9285+
],
9286+
});
9287+
fs.writeFileSync(
9288+
"index.js",
9289+
`export class ExampleDurableObject {}; export default{};`
9290+
);
9291+
fs.writeFileSync(
9292+
"./Dockerfile",
9293+
"FROM node:18\nCOPY . .\nCMD ['node', 'index.js']"
9294+
);
9295+
9296+
// Mock buildAndMaybePush to verify it's called with dryRun=true
9297+
const buildAndMaybePushSpy = vi.spyOn(
9298+
await import("../cloudchamber/build"),
9299+
"buildAndMaybePush"
9300+
);
9301+
buildAndMaybePushSpy.mockImplementation(async () => ({
9302+
image: "test-image:latest",
9303+
pushed: false,
9304+
}));
9305+
9306+
// Mock other dependencies
9307+
vi.doMock("../cloudchamber/common", () => ({
9308+
fillOpenAPIConfiguration: vi.fn(),
9309+
}));
9310+
vi.doMock("../versions/api", () => ({
9311+
fetchVersion: vi.fn().mockResolvedValue({
9312+
resources: {
9313+
bindings: [
9314+
{
9315+
type: "durable_object_namespace",
9316+
class_name: "ExampleDurableObject",
9317+
namespace_id: "test-namespace-id",
9318+
},
9319+
],
9320+
},
9321+
}),
9322+
}));
9323+
9324+
await runWrangler("deploy --dry-run");
9325+
9326+
// Verify buildAndMaybePush was called with dryRun=true
9327+
expect(buildAndMaybePushSpy).toHaveBeenCalledWith(
9328+
expect.objectContaining({
9329+
tag: expect.stringContaining("test-container:"),
9330+
}),
9331+
expect.any(String), // pathToDocker
9332+
false, // push = false in dry-run
9333+
expect.objectContaining({
9334+
name: "test-container",
9335+
class_name: "ExampleDurableObject",
9336+
image: "./Dockerfile",
9337+
}),
9338+
true // dryRun = true
9339+
);
9340+
});
9341+
92739342
it("should support durable objects and D1", async () => {
92749343
writeWranglerConfig({
92759344
main: "index.js",

packages/wrangler/src/cloudchamber/build.ts

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,26 @@ export async function buildAndMaybePush(
7777
args: BuildArgs,
7878
pathToDocker: string,
7979
push: boolean,
80-
containerConfig?: ContainerApp
80+
containerConfig?: ContainerApp,
81+
dryRun = false
8182
): Promise<{ image: string; pushed: boolean }> {
8283
try {
83-
// account is also used to check limits below, so it is better to just pull the entire
84-
// account information here
85-
const account = await loadAccount();
86-
const cloudflareAccountID = account.external_account_id;
84+
// In dry-run mode, we skip account loading and disk limit validation
85+
// since we're not actually deploying
86+
let account: CompleteAccountCustomer | undefined;
87+
let cloudflareAccountID: string;
88+
89+
if (!dryRun) {
90+
// account is also used to check limits below, so it is better to just pull the entire
91+
// account information here
92+
account = await loadAccount();
93+
cloudflareAccountID = account.external_account_id;
94+
} else {
95+
// In dry-run mode, we use a placeholder account ID for the image tag
96+
// The actual account ID won't matter since we're not pushing
97+
cloudflareAccountID = "dry-run-account-id";
98+
}
99+
87100
const imageTag = getCloudflareRegistryWithAccountNamespace(
88101
cloudflareAccountID,
89102
args.tag
@@ -105,27 +118,31 @@ export async function buildAndMaybePush(
105118
dockerfile,
106119
});
107120

108-
// ensure the account is not allowed to build anything that exceeds the current
109-
// account's disk size limits
110-
const inspectOutput = await dockerImageInspect(pathToDocker, {
111-
imageTag,
112-
formatString:
113-
"{{ .Size }} {{ len .RootFS.Layers }} {{json .RepoDigests}}",
114-
});
121+
// Only check disk limits when not in dry-run mode
122+
if (!dryRun && account) {
123+
// ensure the account is not allowed to build anything that exceeds the current
124+
// account's disk size limits
125+
const inspectOutput = await dockerImageInspect(pathToDocker, {
126+
imageTag,
127+
formatString:
128+
"{{ .Size }} {{ len .RootFS.Layers }} {{json .RepoDigests}}",
129+
});
115130

116-
const [sizeStr, layerStr, repoDigests] = inspectOutput.split(" ");
117-
const size = parseInt(sizeStr, 10);
118-
const layers = parseInt(layerStr, 10);
131+
const [sizeStr, layerStr, repoDigests] = inspectOutput.split(" ");
132+
const size = parseInt(sizeStr, 10);
133+
const layers = parseInt(layerStr, 10);
134+
135+
// 16MiB is the layer size adjustments we use in devmapper
136+
const MiB = 1024 * 1024;
137+
const requiredSize = Math.ceil(size * 1.1 + layers * 16 * MiB);
138+
// TODO: do more config merging and earlier
139+
await ensureDiskLimits({
140+
requiredSize,
141+
account: account,
142+
containerApp: containerConfig,
143+
});
144+
}
119145

120-
// 16MiB is the layer size adjustments we use in devmapper
121-
const MiB = 1024 * 1024;
122-
const requiredSize = Math.ceil(size * 1.1 + layers * 16 * MiB);
123-
// TODO: do more config merging and earlier
124-
await ensureDiskLimits({
125-
requiredSize,
126-
account: account,
127-
containerApp: containerConfig,
128-
});
129146
let pushed = false;
130147
if (push) {
131148
await dockerLoginManagedRegistry(pathToDocker);
@@ -140,6 +157,12 @@ export async function buildAndMaybePush(
140157
// pulled every time. This guarantees consistency across different environments
141158
// and deployments.
142159
// From: https://docs.docker.com/dhi/core-concepts/digests/
160+
const inspectOutput = await dockerImageInspect(pathToDocker, {
161+
imageTag,
162+
formatString:
163+
"{{ .Size }} {{ len .RootFS.Layers }} {{json .RepoDigests}}",
164+
});
165+
const [, , repoDigests] = inspectOutput.split(" ");
143166
const parsedDigests = JSON.parse(repoDigests);
144167

145168
if (!Array.isArray(parsedDigests)) {
@@ -220,7 +243,8 @@ export async function buildCommand(
220243
},
221244
getDockerPath() ?? args.pathToDocker,
222245
args.push,
223-
container
246+
container,
247+
false // not a dry-run for regular build command
224248
);
225249
}
226250
}

packages/wrangler/src/cloudchamber/deploy.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ export async function maybeBuildContainer(
4444
options,
4545
pathToDocker,
4646
!dryRun,
47-
containerConfig
47+
containerConfig,
48+
dryRun
4849
);
4950
return buildResult;
5051
}

0 commit comments

Comments
 (0)