Skip to content

Commit cbf1de2

Browse files
committed
Implement updates to provisioning
1 parent 19b1881 commit cbf1de2

File tree

5 files changed

+128
-158
lines changed

5 files changed

+128
-158
lines changed

packages/wrangler/src/api/startDevWorker/utils.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import assert from "node:assert";
22
import { readFile } from "node:fs/promises";
33
import type { ConfigBindingOptions } from "../../config";
4+
import type { WorkerMetadataBinding } from "../../deployment-bundle/create-worker-upload-form";
45
import type { CfWorkerInit } from "../../deployment-bundle/worker";
56
import type {
67
Binding,
@@ -279,8 +280,22 @@ export function convertCfWorkerInitBindingsToBindings(
279280
return output;
280281
}
281282

283+
/**
284+
* Convert either StartDevWorkerOptions["bindings"] or WorkerMetadataBinding[] to CfWorkerInit["bindings"]
285+
* This function is by design temporary, but has lived longer than originally expected.
286+
* For some context, CfWorkerInit is the in-memory representation of a Worker that Wrangler uses,
287+
* WorkerMetadataBinding is the representation of bindings that comes from the API, and StartDevWorkerOptions
288+
* is the "new" in-memory representation of a Worker that's used in Wrangler's dev flow. Over
289+
* time, all uses of CfWorkerInit should transition to StartDevWorkerOptions, but that's a pretty big refactor.
290+
* As such, in the meantime we have conversion functions so that different code paths can deal with the format they
291+
* expect and were written for.
292+
*
293+
* WARNING: Using this with WorkerMetadataBinding[] will lose information about certain
294+
* binding types (i.e. WASM modules, text blobs, and data blobs). These binding types are deprecated
295+
* but may still be used by some Workers in the wild.
296+
*/
282297
export async function convertBindingsToCfWorkerInitBindings(
283-
inputBindings: StartDevWorkerOptions["bindings"]
298+
inputBindings: StartDevWorkerOptions["bindings"] | WorkerMetadataBinding[]
284299
): Promise<{
285300
bindings: CfWorkerInit["bindings"];
286301
fetchers: Record<string, ServiceFetch>;
@@ -317,23 +332,35 @@ export async function convertBindingsToCfWorkerInitBindings(
317332

318333
const fetchers: Record<string, ServiceFetch> = {};
319334

320-
for (const [name, binding] of Object.entries(inputBindings ?? {})) {
335+
const iterator: [string, WorkerMetadataBinding | Binding][] = Array.isArray(
336+
inputBindings
337+
)
338+
? inputBindings.map((b) => [b.name, b])
339+
: Object.entries(inputBindings ?? {});
340+
341+
for (const [name, binding] of iterator) {
321342
if (binding.type === "plain_text") {
322343
bindings.vars ??= {};
323-
bindings.vars[name] = binding.value;
344+
bindings.vars[name] = "value" in binding ? binding.value : binding.text;
324345
} else if (binding.type === "json") {
325346
bindings.vars ??= {};
326-
bindings.vars[name] = binding.value;
347+
bindings.vars[name] = "value" in binding ? binding.value : binding.json;
327348
} else if (binding.type === "kv_namespace") {
328349
bindings.kv_namespaces ??= [];
329350
bindings.kv_namespaces.push({ ...binding, binding: name });
330351
} else if (binding.type === "send_email") {
331352
bindings.send_email ??= [];
332353
bindings.send_email.push({ ...binding, name: name });
333354
} else if (binding.type === "wasm_module") {
355+
if (!("source" in binding)) {
356+
continue;
357+
}
334358
bindings.wasm_modules ??= {};
335359
bindings.wasm_modules[name] = await getBinaryFileContents(binding.source);
336360
} else if (binding.type === "text_blob") {
361+
if (!("source" in binding)) {
362+
continue;
363+
}
337364
bindings.text_blobs ??= {};
338365

339366
if (typeof binding.source.path === "string") {
@@ -345,6 +372,9 @@ export async function convertBindingsToCfWorkerInitBindings(
345372
);
346373
}
347374
} else if (binding.type === "data_blob") {
375+
if (!("source" in binding)) {
376+
continue;
377+
}
348378
bindings.data_blobs ??= {};
349379
bindings.data_blobs[name] = await getBinaryFileContents(binding.source);
350380
} else if (binding.type === "browser") {
@@ -383,7 +413,14 @@ export async function convertBindingsToCfWorkerInitBindings(
383413
bindings.analytics_engine_datasets.push({ ...binding, binding: name });
384414
} else if (binding.type === "dispatch_namespace") {
385415
bindings.dispatch_namespaces ??= [];
386-
bindings.dispatch_namespaces.push({ ...binding, binding: name });
416+
bindings.dispatch_namespaces.push({
417+
...binding,
418+
binding: name,
419+
outbound:
420+
binding.outbound && "worker" in binding.outbound
421+
? undefined
422+
: binding.outbound,
423+
});
387424
} else if (binding.type === "mtls_certificate") {
388425
bindings.mtls_certificates ??= [];
389426
bindings.mtls_certificates.push({ ...binding, binding: name });

packages/wrangler/src/deploy/deploy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
847847
}
848848
}
849849

850-
workerBundle = createWorkerUploadForm(worker);
850+
workerBundle = createWorkerUploadForm(worker, { dryRun: true });
851851
printBindings(
852852
{ ...withoutStaticAssets, vars: maskedVars },
853853
config.tail_consumers,

packages/wrangler/src/deployment-bundle/bindings.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import assert from "node:assert";
22
import { fetchResult } from "../cfetch";
3+
import { experimental_patchConfig } from "../cli";
4+
import { PatchConfigError } from "../config/patch-config";
35
import { createD1Database } from "../d1/create";
46
import { listDatabases } from "../d1/list";
57
import { getDatabaseInfoFromIdOrName } from "../d1/utils";
68
import { prompt, select } from "../dialogs";
79
import { UserError } from "../errors";
10+
import { isNonInteractiveOrCI } from "../is-interactive";
811
import { createKVNamespace, listKVNamespaces } from "../kv/helpers";
912
import { logger } from "../logger";
1013
import * as metrics from "../metrics";
1114
import { APIError } from "../parse";
1215
import { createR2Bucket, getR2Bucket, listR2Buckets } from "../r2/helpers";
1316
import { isLegacyEnv } from "../utils/isLegacyEnv";
1417
import { printBindings } from "../utils/print-bindings";
15-
import type { Config } from "../config";
18+
import type { Config, RawConfig } from "../config";
1619
import type { ComplianceConfig } from "../environment-variables/misc-variables";
1720
import type { WorkerMetadataBinding } from "./create-worker-upload-form";
1821
import type {
@@ -161,6 +164,13 @@ class R2Handler extends ProvisionResourceHandler<"r2_bucket", CfR2Bucket> {
161164
get name(): string | undefined {
162165
return this.binding.bucket_name as string;
163166
}
167+
168+
override inherit(): void {
169+
if (!this.binding.bucket_name) {
170+
this.binding.bucket_name = INHERIT_SYMBOL;
171+
}
172+
}
173+
164174
async create(name: string) {
165175
await createR2Bucket(
166176
this.complianceConfig,
@@ -183,7 +193,10 @@ class R2Handler extends ProvisionResourceHandler<"r2_bucket", CfR2Bucket> {
183193
(existing) =>
184194
existing.type === this.type &&
185195
existing.name === this.binding.binding &&
186-
existing.jurisdiction === this.binding.jurisdiction
196+
existing.jurisdiction === this.binding.jurisdiction &&
197+
(this.binding.bucket_name
198+
? this.binding.bucket_name === existing.bucket_name
199+
: true)
187200
);
188201
}
189202
async isConnectedToExistingResource(): Promise<boolean> {
@@ -423,6 +436,7 @@ async function collectPendingResources(
423436
(a, b) => HANDLERS[a.resourceType].sort - HANDLERS[b.resourceType].sort
424437
);
425438
}
439+
426440
export async function provisionBindings(
427441
bindings: CfWorkerInit["bindings"],
428442
accountId: string,
@@ -438,6 +452,11 @@ export async function provisionBindings(
438452
);
439453

440454
if (pendingResources.length > 0) {
455+
if (!config.configPath) {
456+
throw new UserError(
457+
"Provisioning resources is not supported without a config file"
458+
);
459+
}
441460
if (!isLegacyEnv(config)) {
442461
throw new UserError(
443462
"Provisioning resources is not supported with a service environment"
@@ -468,6 +487,45 @@ export async function provisionBindings(
468487
);
469488
}
470489

490+
const patch: RawConfig = {};
491+
492+
for (const resource of pendingResources) {
493+
patch[resource.resourceType] = config[resource.resourceType].map(
494+
(binding) => {
495+
if (binding.binding === resource.binding) {
496+
// Using an early return here would be nicer but makes TS blow up
497+
binding = resource.handler.binding;
498+
}
499+
500+
return Object.fromEntries(
501+
Object.entries(binding).filter(
502+
// Make sure all the values are JSON serialisable.
503+
// Otherwise we end up with "undefined" in the config
504+
([_, value]) => typeof value === "string"
505+
)
506+
) as typeof binding;
507+
}
508+
);
509+
}
510+
511+
// If the user is performing an interactive deploy, write the provisioned IDs back to the config file.
512+
// This is not necessary, as future deploys can use inherited resources, but it can help with
513+
// portability of the config file, and adds robustness to bindings being renamed.
514+
if (!isNonInteractiveOrCI()) {
515+
try {
516+
await experimental_patchConfig(config.configPath, patch, false);
517+
logger.log(
518+
"Your Worker was deployed with provisioned resources. We've written the IDs of these resources to your config file, which you can choose to save or discard—either way future deploys will continue to work."
519+
);
520+
} catch (e) {
521+
if (e instanceof PatchConfigError) {
522+
// no-op — if the user is using TOML config we can't update it.
523+
} else {
524+
throw e;
525+
}
526+
}
527+
}
528+
471529
const resourceCount = pendingResources.reduce(
472530
(acc, resource) => {
473531
acc[resource.resourceType] ??= 0;

packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,10 @@ export type WorkerMetadata = WorkerMetadataPut | WorkerMetadataVersionsPost;
207207
/**
208208
* Creates a `FormData` upload from a `CfWorkerInit`.
209209
*/
210-
export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
210+
export function createWorkerUploadForm(
211+
worker: CfWorkerInit,
212+
options?: { dryRun: true }
213+
): FormData {
211214
const formData = new FormData();
212215
const {
213216
main,
@@ -267,6 +270,14 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
267270
});
268271

269272
bindings.kv_namespaces?.forEach(({ id, binding, raw }) => {
273+
// If we're doing a dry run there's no way to know whether or not a KV namespace
274+
// is inheritable or requires provisioning (since that would require hitting the API).
275+
// As such, _assume_ any undefined IDs are inheritable when doing a dry run.
276+
// When this Worker is actually deployed, some may be provisioned at the point of deploy
277+
if (options?.dryRun && id === undefined) {
278+
id = INHERIT_SYMBOL;
279+
}
280+
270281
if (id === undefined) {
271282
throw new UserError(`${binding} bindings must have an "id" field`);
272283
}
@@ -340,6 +351,9 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
340351

341352
bindings.r2_buckets?.forEach(
342353
({ binding, bucket_name, jurisdiction, raw }) => {
354+
if (options?.dryRun && bucket_name === undefined) {
355+
bucket_name = INHERIT_SYMBOL;
356+
}
343357
if (bucket_name === undefined) {
344358
throw new UserError(
345359
`${binding} bindings must have a "bucket_name" field`
@@ -365,6 +379,9 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
365379

366380
bindings.d1_databases?.forEach(
367381
({ binding, database_id, database_internal_env, raw }) => {
382+
if (options?.dryRun && database_id === undefined) {
383+
database_id = INHERIT_SYMBOL;
384+
}
368385
if (database_id === undefined) {
369386
throw new UserError(
370387
`${binding} bindings must have a "database_id" field`

0 commit comments

Comments
 (0)