Skip to content

Commit fbeb554

Browse files
committed
Implement updates to provisioning
1 parent d0801b1 commit fbeb554

File tree

5 files changed

+128
-161
lines changed

5 files changed

+128
-161
lines changed

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

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import assert from "node:assert";
22
import { readFile } from "node:fs/promises";
33
import { assertNever } from "../../utils/assert-never";
44
import type { ConfigBindingOptions } from "../../config";
5+
import type { WorkerMetadataBinding } from "../../deployment-bundle/create-worker-upload-form";
56
import type { CfWorkerInit } from "../../deployment-bundle/worker";
67
import type {
78
Binding,
@@ -308,8 +309,22 @@ export function convertCfWorkerInitBindingsToBindings(
308309
return output;
309310
}
310311

312+
/**
313+
* Convert either StartDevWorkerOptions["bindings"] or WorkerMetadataBinding[] to CfWorkerInit["bindings"]
314+
* This function is by design temporary, but has lived longer than originally expected.
315+
* For some context, CfWorkerInit is the in-memory representation of a Worker that Wrangler uses,
316+
* WorkerMetadataBinding is the representation of bindings that comes from the API, and StartDevWorkerOptions
317+
* is the "new" in-memory representation of a Worker that's used in Wrangler's dev flow. Over
318+
* time, all uses of CfWorkerInit should transition to StartDevWorkerOptions, but that's a pretty big refactor.
319+
* As such, in the meantime we have conversion functions so that different code paths can deal with the format they
320+
* expect and were written for.
321+
*
322+
* WARNING: Using this with WorkerMetadataBinding[] will lose information about certain
323+
* binding types (i.e. WASM modules, text blobs, and data blobs). These binding types are deprecated
324+
* but may still be used by some Workers in the wild.
325+
*/
311326
export async function convertBindingsToCfWorkerInitBindings(
312-
inputBindings: StartDevWorkerOptions["bindings"]
327+
inputBindings: StartDevWorkerOptions["bindings"] | WorkerMetadataBinding[]
313328
): Promise<{
314329
bindings: CfWorkerInit["bindings"];
315330
fetchers: Record<string, ServiceFetch>;
@@ -349,23 +364,35 @@ export async function convertBindingsToCfWorkerInitBindings(
349364

350365
const fetchers: Record<string, ServiceFetch> = {};
351366

352-
for (const [name, binding] of Object.entries(inputBindings ?? {})) {
367+
const iterator: [string, WorkerMetadataBinding | Binding][] = Array.isArray(
368+
inputBindings
369+
)
370+
? inputBindings.map((b) => [b.name, b])
371+
: Object.entries(inputBindings ?? {});
372+
373+
for (const [name, binding] of iterator) {
353374
if (binding.type === "plain_text") {
354375
bindings.vars ??= {};
355-
bindings.vars[name] = binding.value;
376+
bindings.vars[name] = "value" in binding ? binding.value : binding.text;
356377
} else if (binding.type === "json") {
357378
bindings.vars ??= {};
358-
bindings.vars[name] = binding.value;
379+
bindings.vars[name] = "value" in binding ? binding.value : binding.json;
359380
} else if (binding.type === "kv_namespace") {
360381
bindings.kv_namespaces ??= [];
361382
bindings.kv_namespaces.push({ ...binding, binding: name });
362383
} else if (binding.type === "send_email") {
363384
bindings.send_email ??= [];
364385
bindings.send_email.push({ ...binding, name: name });
365386
} else if (binding.type === "wasm_module") {
387+
if (!("source" in binding)) {
388+
continue;
389+
}
366390
bindings.wasm_modules ??= {};
367391
bindings.wasm_modules[name] = await getBinaryFileContents(binding.source);
368392
} else if (binding.type === "text_blob") {
393+
if (!("source" in binding)) {
394+
continue;
395+
}
369396
bindings.text_blobs ??= {};
370397

371398
if (typeof binding.source.path === "string") {
@@ -377,6 +404,9 @@ export async function convertBindingsToCfWorkerInitBindings(
377404
);
378405
}
379406
} else if (binding.type === "data_blob") {
407+
if (!("source" in binding)) {
408+
continue;
409+
}
380410
bindings.data_blobs ??= {};
381411
bindings.data_blobs[name] = await getBinaryFileContents(binding.source);
382412
} else if (binding.type === "browser") {
@@ -415,7 +445,14 @@ export async function convertBindingsToCfWorkerInitBindings(
415445
bindings.analytics_engine_datasets.push({ ...binding, binding: name });
416446
} else if (binding.type === "dispatch_namespace") {
417447
bindings.dispatch_namespaces ??= [];
418-
bindings.dispatch_namespaces.push({ ...binding, binding: name });
448+
bindings.dispatch_namespaces.push({
449+
...binding,
450+
binding: name,
451+
outbound:
452+
binding.outbound && "worker" in binding.outbound
453+
? undefined
454+
: binding.outbound,
455+
});
419456
} else if (binding.type === "mtls_certificate") {
420457
bindings.mtls_certificates ??= [];
421458
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
@@ -855,7 +855,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
855855
}
856856
}
857857

858-
workerBundle = createWorkerUploadForm(worker);
858+
workerBundle = createWorkerUploadForm(worker, { dryRun: true });
859859
printBindings(
860860
{ ...withoutStaticAssets, vars: maskedVars },
861861
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 {
@@ -164,6 +167,13 @@ class R2Handler extends ProvisionResourceHandler<"r2_bucket", CfR2Bucket> {
164167
get name(): string | undefined {
165168
return this.binding.bucket_name as string;
166169
}
170+
171+
override inherit(): void {
172+
if (!this.binding.bucket_name) {
173+
this.binding.bucket_name = INHERIT_SYMBOL;
174+
}
175+
}
176+
167177
async create(name: string) {
168178
await createR2Bucket(
169179
this.complianceConfig,
@@ -186,7 +196,10 @@ class R2Handler extends ProvisionResourceHandler<"r2_bucket", CfR2Bucket> {
186196
(existing) =>
187197
existing.type === this.type &&
188198
existing.name === this.binding.binding &&
189-
existing.jurisdiction === this.binding.jurisdiction
199+
existing.jurisdiction === this.binding.jurisdiction &&
200+
(this.binding.bucket_name
201+
? this.binding.bucket_name === existing.bucket_name
202+
: true)
190203
);
191204
}
192205
async isConnectedToExistingResource(): Promise<boolean> {
@@ -426,6 +439,7 @@ async function collectPendingResources(
426439
(a, b) => HANDLERS[a.resourceType].sort - HANDLERS[b.resourceType].sort
427440
);
428441
}
442+
429443
export async function provisionBindings(
430444
bindings: CfWorkerInit["bindings"],
431445
accountId: string,
@@ -441,6 +455,11 @@ export async function provisionBindings(
441455
);
442456

443457
if (pendingResources.length > 0) {
458+
if (!config.configPath) {
459+
throw new UserError(
460+
"Provisioning resources is not supported without a config file"
461+
);
462+
}
444463
if (!isLegacyEnv(config)) {
445464
throw new UserError(
446465
"Provisioning resources is not supported with a service environment"
@@ -471,6 +490,45 @@ export async function provisionBindings(
471490
);
472491
}
473492

493+
const patch: RawConfig = {};
494+
495+
for (const resource of pendingResources) {
496+
patch[resource.resourceType] = config[resource.resourceType].map(
497+
(binding) => {
498+
if (binding.binding === resource.binding) {
499+
// Using an early return here would be nicer but makes TS blow up
500+
binding = resource.handler.binding;
501+
}
502+
503+
return Object.fromEntries(
504+
Object.entries(binding).filter(
505+
// Make sure all the values are JSON serialisable.
506+
// Otherwise we end up with "undefined" in the config
507+
([_, value]) => typeof value === "string"
508+
)
509+
) as typeof binding;
510+
}
511+
);
512+
}
513+
514+
// If the user is performing an interactive deploy, write the provisioned IDs back to the config file.
515+
// This is not necessary, as future deploys can use inherited resources, but it can help with
516+
// portability of the config file, and adds robustness to bindings being renamed.
517+
if (!isNonInteractiveOrCI()) {
518+
try {
519+
await experimental_patchConfig(config.configPath, patch, false);
520+
logger.log(
521+
"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."
522+
);
523+
} catch (e) {
524+
if (e instanceof PatchConfigError) {
525+
// no-op — if the user is using TOML config we can't update it.
526+
} else {
527+
throw e;
528+
}
529+
}
530+
}
531+
474532
const resourceCount = pendingResources.reduce(
475533
(acc, resource) => {
476534
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
@@ -219,7 +219,10 @@ export type WorkerMetadata = WorkerMetadataPut | WorkerMetadataVersionsPost;
219219
/**
220220
* Creates a `FormData` upload from a `CfWorkerInit`.
221221
*/
222-
export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
222+
export function createWorkerUploadForm(
223+
worker: CfWorkerInit,
224+
options?: { dryRun: true }
225+
): FormData {
223226
const formData = new FormData();
224227
const {
225228
main,
@@ -279,6 +282,14 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
279282
});
280283

281284
bindings.kv_namespaces?.forEach(({ id, binding, raw }) => {
285+
// If we're doing a dry run there's no way to know whether or not a KV namespace
286+
// is inheritable or requires provisioning (since that would require hitting the API).
287+
// As such, _assume_ any undefined IDs are inheritable when doing a dry run.
288+
// When this Worker is actually deployed, some may be provisioned at the point of deploy
289+
if (options?.dryRun && id === undefined) {
290+
id = INHERIT_SYMBOL;
291+
}
292+
282293
if (id === undefined) {
283294
throw new UserError(`${binding} bindings must have an "id" field`);
284295
}
@@ -357,6 +368,9 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
357368

358369
bindings.r2_buckets?.forEach(
359370
({ binding, bucket_name, jurisdiction, raw }) => {
371+
if (options?.dryRun && bucket_name === undefined) {
372+
bucket_name = INHERIT_SYMBOL;
373+
}
360374
if (bucket_name === undefined) {
361375
throw new UserError(
362376
`${binding} bindings must have a "bucket_name" field`
@@ -382,6 +396,9 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
382396

383397
bindings.d1_databases?.forEach(
384398
({ binding, database_id, database_internal_env, raw }) => {
399+
if (options?.dryRun && database_id === undefined) {
400+
database_id = INHERIT_SYMBOL;
401+
}
385402
if (database_id === undefined) {
386403
throw new UserError(
387404
`${binding} bindings must have a "database_id" field`

0 commit comments

Comments
 (0)