From 133c0423ccb4c2b35a1dd26157ce9a24c6a743bb Mon Sep 17 00:00:00 2001 From: Zeb Piasecki Date: Mon, 8 May 2023 12:55:11 -0400 Subject: [PATCH] [wrangler] feat: add support for placement in wrangler config (#3095) * [wrangler] feat: add support for placement in wrangler config Allows a placement object in the wrangler config with a mode of off or smart to configure Smart placement. * increase specificity for workers placement * Add example to placement changeset --- .changeset/short-bottles-smell.md | 12 +++++++++ .../src/__tests__/configuration.test.ts | 8 ++++++ .../pages/create-worker-bundle-contents.ts | 1 + packages/wrangler/src/config/environment.ts | 7 +++++ packages/wrangler/src/config/validation.ts | 27 +++++++++++++++++++ .../wrangler/src/create-worker-upload-form.ts | 4 +++ packages/wrangler/src/dev/remote.tsx | 1 + packages/wrangler/src/init.ts | 5 ++++ packages/wrangler/src/publish/publish.ts | 7 ++++- packages/wrangler/src/secret/index.ts | 1 + packages/wrangler/src/worker.ts | 5 ++++ 11 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 .changeset/short-bottles-smell.md diff --git a/.changeset/short-bottles-smell.md b/.changeset/short-bottles-smell.md new file mode 100644 index 000000000000..e32fcf01b3b4 --- /dev/null +++ b/.changeset/short-bottles-smell.md @@ -0,0 +1,12 @@ +--- +"wrangler": minor +--- + +feat: add support for placement in wrangler config + +Allows a `placement` object in the wrangler config with a mode of `off` or `smart` to configure [Smart placement](https://developers.cloudflare.com/workers/platform/smart-placement/). Enabling Smart Placement can be done in your `wrangler.toml` like: + +```toml +[placement] +mode = "smart" +``` diff --git a/packages/wrangler/src/__tests__/configuration.test.ts b/packages/wrangler/src/__tests__/configuration.test.ts index e7012f1ee6e3..86d0fafb9ae5 100644 --- a/packages/wrangler/src/__tests__/configuration.test.ts +++ b/packages/wrangler/src/__tests__/configuration.test.ts @@ -84,6 +84,7 @@ describe("normalizeAndValidateConfig()", () => { first_party_worker: undefined, keep_vars: undefined, logpush: undefined, + placement: undefined, }); expect(diagnostics.hasErrors()).toBe(false); expect(diagnostics.hasWarnings()).toBe(false); @@ -955,6 +956,9 @@ describe("normalizeAndValidateConfig()", () => { node_compat: true, first_party_worker: true, logpush: true, + placement: { + mode: "smart", + }, }; const { config, diagnostics } = normalizeAndValidateConfig( @@ -1030,6 +1034,9 @@ describe("normalizeAndValidateConfig()", () => { node_compat: "INVALID", first_party_worker: "INVALID", logpush: "INVALID", + placement: { + mode: "INVALID", + }, } as unknown as RawEnvironment; const { config, diagnostics } = normalizeAndValidateConfig( @@ -1093,6 +1100,7 @@ describe("normalizeAndValidateConfig()", () => { - Expected \\"name\\" to be of type string, alphanumeric and lowercase with dashes only but got 111. - Expected \\"main\\" to be of type string but got 1333. - Expected \\"usage_model\\" field to be one of [\\"bundled\\",\\"unbound\\"] but got \\"INVALID\\". + - Expected \\"placement.mode\\" field to be one of [\\"off\\",\\"smart\\"] but got \\"INVALID\\". - The field \\"define.DEF1\\" should be a string but got 1777. - Expected \\"no_bundle\\" to be of type boolean but got \\"INVALID\\". - Expected \\"minify\\" to be of type boolean but got \\"INVALID\\". diff --git a/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts b/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts index db90ab954d4a..e44c16a03533 100644 --- a/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts +++ b/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts @@ -70,6 +70,7 @@ function createWorkerBundleFormData(workerBundle: BundleResult): FormData { usage_model: undefined, keepVars: undefined, logpush: undefined, + placement: undefined, }; return createWorkerUploadForm(worker); diff --git a/packages/wrangler/src/config/environment.ts b/packages/wrangler/src/config/environment.ts index ae9bdab8d2d1..092c47851b01 100644 --- a/packages/wrangler/src/config/environment.ts +++ b/packages/wrangler/src/config/environment.ts @@ -265,6 +265,13 @@ interface EnvironmentInheritable { * @inheritable */ logpush: boolean | undefined; + + /** + * Specify how the worker should be located to minimize round-trip time. + * + * More details: https://developers.cloudflare.com/workers/platform/smart-placement/ + */ + placement: { mode: "off" | "smart" } | undefined; } export type DurableObjectBindings = { diff --git a/packages/wrangler/src/config/validation.ts b/packages/wrangler/src/config/validation.ts index 5fbd63662125..51b063349b06 100644 --- a/packages/wrangler/src/config/validation.ts +++ b/packages/wrangler/src/config/validation.ts @@ -873,6 +873,32 @@ function validateRoutes( ); } +function normalizeAndValidatePlacement( + diagnostics: Diagnostics, + topLevelEnv: Environment | undefined, + rawEnv: RawEnvironment +): Config["placement"] { + if (rawEnv.placement) { + validateRequiredProperty( + diagnostics, + "placement", + "mode", + rawEnv.placement.mode, + "string", + ["off", "smart"] + ); + } + + return inheritable( + diagnostics, + topLevelEnv, + rawEnv, + "placement", + () => true, + undefined + ); +} + /** * Validate top-level environment configuration and return the normalized values. */ @@ -1060,6 +1086,7 @@ function normalizeAndValidateEnvironment( isOneOf("bundled", "unbound"), undefined ), + placement: normalizeAndValidatePlacement(diagnostics, topLevelEnv, rawEnv), build, workers_dev, // Not inherited fields diff --git a/packages/wrangler/src/create-worker-upload-form.ts b/packages/wrangler/src/create-worker-upload-form.ts index 5899e844d4d0..592979bd9621 100644 --- a/packages/wrangler/src/create-worker-upload-form.ts +++ b/packages/wrangler/src/create-worker-upload-form.ts @@ -4,6 +4,7 @@ import type { CfWorkerInit, CfModuleType, CfDurableObjectMigrations, + CfPlacement, } from "./worker.js"; export function toMimeType(type: CfModuleType): string { @@ -71,6 +72,7 @@ export interface WorkerMetadata { bindings: WorkerMetadataBinding[]; keep_bindings?: WorkerMetadataBinding["type"][]; logpush?: boolean; + placement?: CfPlacement; // Allow unsafe.metadata to add arbitary properties at runtime [key: string]: unknown; } @@ -89,6 +91,7 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData { compatibility_flags, keepVars, logpush, + placement, } = worker; let { modules } = worker; @@ -320,6 +323,7 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData { capnp_schema: bindings.logfwdr?.schema, ...(keepVars && { keep_bindings: ["plain_text", "json"] }), ...(logpush !== undefined && { logpush }), + ...(placement && { placement }), }; if (bindings.unsafe?.metadata !== undefined) { diff --git a/packages/wrangler/src/dev/remote.tsx b/packages/wrangler/src/dev/remote.tsx index f6d9461c0aa5..162668e65bf7 100644 --- a/packages/wrangler/src/dev/remote.tsx +++ b/packages/wrangler/src/dev/remote.tsx @@ -576,6 +576,7 @@ async function createRemoteWorkerInit(props: { usage_model: props.usageModel, keepVars: true, logpush: false, + placement: undefined, // no placement in dev }; return init; diff --git a/packages/wrangler/src/init.ts b/packages/wrangler/src/init.ts index 9f1249553a39..6b0bb26b55f3 100644 --- a/packages/wrangler/src/init.ts +++ b/packages/wrangler/src/init.ts @@ -80,6 +80,7 @@ export type ServiceMetadataRes = { usage_model: "bundled" | "unbound"; compatibility_date: string; last_deployed_from?: "wrangler" | "dash" | "api"; + placement_mode?: "smart"; }; }; created_on: string; @@ -868,6 +869,10 @@ async function getWorkerConfig( new Date().toISOString().substring(0, 10), ...routeOrRoutesToConfig, usage_model: serviceEnvMetadata.script.usage_model, + placement: + serviceEnvMetadata.script.placement_mode === "smart" + ? { mode: "smart" } + : undefined, ...(durableObjectClassNames.length ? { migrations: [ diff --git a/packages/wrangler/src/publish/publish.ts b/packages/wrangler/src/publish/publish.ts index 54edff74c897..8c163fe4d571 100644 --- a/packages/wrangler/src/publish/publish.ts +++ b/packages/wrangler/src/publish/publish.ts @@ -35,7 +35,7 @@ import type { import type { Entry } from "../entry"; import type { PutConsumerBody } from "../queues/client"; import type { AssetPaths } from "../sites"; -import type { CfWorkerInit } from "../worker"; +import type { CfWorkerInit, CfPlacement } from "../worker"; type Props = { config: Config; @@ -571,6 +571,10 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m }); } + // The upload API only accepts an empty string or no specified placement for the "off" mode. + const placement: CfPlacement | undefined = + config.placement?.mode === "smart" ? { mode: "smart" } : undefined; + const worker: CfWorkerInit = { name: scriptName, main: { @@ -586,6 +590,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m usage_model: config.usage_model, keepVars, logpush: props.logpush !== undefined ? props.logpush : config.logpush, + placement, }; // As this is not deterministic for testing, we detect if in a jest environment and run asynchronously diff --git a/packages/wrangler/src/secret/index.ts b/packages/wrangler/src/secret/index.ts index 7f305c42c394..c3f0be359d26 100644 --- a/packages/wrangler/src/secret/index.ts +++ b/packages/wrangler/src/secret/index.ts @@ -123,6 +123,7 @@ export const secret = (secretYargs: CommonYargsArgv) => { usage_model: undefined, keepVars: false, // this doesn't matter since it's a new script anyway logpush: false, + placement: undefined, }), } ); diff --git a/packages/wrangler/src/worker.ts b/packages/wrangler/src/worker.ts index 59689434c006..9d02b12b5ac1 100644 --- a/packages/wrangler/src/worker.ts +++ b/packages/wrangler/src/worker.ts @@ -203,6 +203,10 @@ export interface CfDurableObjectMigrations { }[]; } +export interface CfPlacement { + mode: "smart"; +} + /** * Options for creating a `CfWorker`. */ @@ -246,6 +250,7 @@ export interface CfWorkerInit { usage_model: "bundled" | "unbound" | undefined; keepVars: boolean | undefined; logpush: boolean | undefined; + placement: CfPlacement | undefined; } export interface CfWorkerContext {