From d538973179966f742edd48958bf311764f715bda Mon Sep 17 00:00:00 2001 From: Jacob M-G Evans <27247160+JacobMGEvans@users.noreply.github.com> Date: Tue, 26 Sep 2023 09:55:45 -0500 Subject: [PATCH] Draft Worker for Bulk Secrets (#4028) When Secrets are uploaded if there is no Worker in place already it will fail, this allows for the same draft worker logic to retry adding the secrets --- .changeset/pretty-avocados-worry.md | 9 + .../wrangler/src/__tests__/secret.test.ts | 12 +- packages/wrangler/src/secret/index.ts | 202 ++++++++++-------- 3 files changed, 132 insertions(+), 91 deletions(-) create mode 100644 .changeset/pretty-avocados-worry.md diff --git a/.changeset/pretty-avocados-worry.md b/.changeset/pretty-avocados-worry.md new file mode 100644 index 000000000000..0703c912376d --- /dev/null +++ b/.changeset/pretty-avocados-worry.md @@ -0,0 +1,9 @@ +--- +"wrangler": patch +--- + +fix: Bulk Secret Draft Worker + +Fixes the issue of a upload of a Secret when a Worker doesn't exist yet, the draft worker is created and the secret is uploaded to it. + +Fixes https://github.com/cloudflare/wrangler-action/issues/162 diff --git a/packages/wrangler/src/__tests__/secret.test.ts b/packages/wrangler/src/__tests__/secret.test.ts index e084376c0a46..496b42905516 100644 --- a/packages/wrangler/src/__tests__/secret.test.ts +++ b/packages/wrangler/src/__tests__/secret.test.ts @@ -678,28 +678,28 @@ describe("wrangler secret", () => { expect(std.err).toMatchInlineSnapshot(` "X [ERROR] uploading secret for key: secret-name-1: - request to + request to https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets failed, reason: Failed to create secret 1 X [ERROR] uploading secret for key: secret-name-3: - request to + request to https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets failed, reason: Failed to create secret 3 X [ERROR] uploading secret for key: secret-name-5: - request to + request to https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets failed, reason: Failed to create secret 5 X [ERROR] uploading secret for key: secret-name-7: - request to + request to https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets failed, reason: Failed to create secret 7 @@ -750,14 +750,14 @@ describe("wrangler secret", () => { expect(std.err).toMatchInlineSnapshot(` "X [ERROR] uploading secret for key: secret-name-1: - request to + request to https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets failed, reason: Failed to create secret 1 X [ERROR] uploading secret for key: secret-name-2: - request to + request to https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets failed, reason: Failed to create secret 2 diff --git a/packages/wrangler/src/secret/index.ts b/packages/wrangler/src/secret/index.ts index bb79a8fac4cf..31a62198b5ee 100644 --- a/packages/wrangler/src/secret/index.ts +++ b/packages/wrangler/src/secret/index.ts @@ -14,11 +14,86 @@ import * as metrics from "../metrics"; import { parseJSON, readFileSync } from "../parse"; import { requireAuth } from "../user"; +import type { Config } from "../config"; import type { CommonYargsArgv, StrictYargsOptionsToInterface, } from "../yargs-types"; +function isMissingWorkerError(e: unknown): e is { code: 10007 } { + return ( + typeof e === "object" && + e !== null && + (e as { code: number }).code === 10007 + ); +} + +async function createDraftWorker({ + config, + args, + accountId, + scriptName, +}: { + config: Config; + args: { env?: string; name?: string }; + accountId: string; + scriptName: string; +}) { + // TODO: log a warning + await fetchResult( + !isLegacyEnv(config) && args.env + ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}` + : `/accounts/${accountId}/workers/scripts/${scriptName}`, + { + method: "PUT", + body: createWorkerUploadForm({ + name: scriptName, + main: { + name: scriptName, + filePath: undefined, + content: `export default { fetch() {} }`, + type: "esm", + }, + bindings: { + kv_namespaces: [], + send_email: [], + vars: {}, + durable_objects: { bindings: [] }, + queues: [], + r2_buckets: [], + d1_databases: [], + vectorize: [], + constellation: [], + services: [], + analytics_engine_datasets: [], + wasm_modules: {}, + browser: undefined, + ai: undefined, + text_blobs: {}, + data_blobs: {}, + dispatch_namespaces: [], + mtls_certificates: [], + logfwdr: { bindings: [] }, + unsafe: { + bindings: undefined, + metadata: undefined, + capnp: undefined, + }, + }, + modules: [], + migrations: undefined, + compatibility_date: undefined, + compatibility_flags: undefined, + usage_model: undefined, + keepVars: false, // this doesn't matter since it's a new script anyway + logpush: false, + placement: undefined, + tail_consumers: undefined, + }), + } + ); +} + export const secret = (secretYargs: CommonYargsArgv) => { return secretYargs .option("legacy-env", { @@ -84,70 +159,6 @@ export const secret = (secretYargs: CommonYargsArgv) => { }); } - const createDraftWorker = async () => { - // TODO: log a warning - await fetchResult( - !isLegacyEnv(config) && args.env - ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}` - : `/accounts/${accountId}/workers/scripts/${scriptName}`, - { - method: "PUT", - body: createWorkerUploadForm({ - name: scriptName, - main: { - name: scriptName, - filePath: undefined, - content: `export default { fetch() {} }`, - type: "esm", - }, - bindings: { - kv_namespaces: [], - send_email: [], - vars: {}, - durable_objects: { bindings: [] }, - queues: [], - r2_buckets: [], - d1_databases: [], - vectorize: [], - constellation: [], - services: [], - analytics_engine_datasets: [], - wasm_modules: {}, - browser: undefined, - ai: undefined, - text_blobs: {}, - data_blobs: {}, - dispatch_namespaces: [], - mtls_certificates: [], - logfwdr: { bindings: [] }, - unsafe: { - bindings: undefined, - metadata: undefined, - capnp: undefined, - }, - }, - modules: [], - migrations: undefined, - compatibility_date: undefined, - compatibility_flags: undefined, - usage_model: undefined, - keepVars: false, // this doesn't matter since it's a new script anyway - logpush: false, - placement: undefined, - tail_consumers: undefined, - }), - } - ); - }; - - function isMissingWorkerError(e: unknown): e is { code: 10007 } { - return ( - typeof e === "object" && - e !== null && - (e as { code: number }).code === 10007 - ); - } - try { await submitSecret(); await metrics.sendMetricsEvent("create encrypted variable", { @@ -156,7 +167,12 @@ export const secret = (secretYargs: CommonYargsArgv) => { } catch (e) { if (isMissingWorkerError(e)) { // create a draft worker and try again - await createDraftWorker(); + await createDraftWorker({ + config, + args, + accountId, + scriptName, + }); await submitSecret(); // TODO: delete the draft worker if this failed too? } else { @@ -363,33 +379,49 @@ export const secretBulkHandler = async (secretBulkArgs: SecretBulkArgs) => { return logger.error(`🚨 No content found in JSON file or piped input.`); } - const url = - !secretBulkArgs.env || isLegacyEnv(config) - ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets` - : `/accounts/${accountId}/workers/services/${scriptName}/environments/${secretBulkArgs.env}/secrets`; + function submitSecrets(key: string, value: string) { + const url = + !secretBulkArgs.env || isLegacyEnv(config) + ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets` + : `/accounts/${accountId}/workers/services/${scriptName}/environments/${secretBulkArgs.env}/secrets`; + + return fetchResult(url, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: key, + text: value, + type: "secret_text", + }), + }); + } // Until we have a bulk route for secrets, we need to make a request for each key/value pair const bulkOutcomes = await Promise.all( Object.entries(content).map(async ([key, value]) => { - return fetchResult(url, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - name: key, - text: value, - type: "secret_text", - }), - }) - .then(() => { - logger.log(`✨ Successfully created secret for key: ${key}`); - return true; - }) - .catch((e) => { + try { + await submitSecrets(key, value); + logger.log(`✨ Successfully created secret for key: ${key}`); + return true; + } catch (e) { + if (e instanceof Error) { logger.error( `uploading secret for key: ${key}: - ${e.message}` + ${e.message}` ); + if (isMissingWorkerError(e)) { + // create a draft worker and try again + await createDraftWorker({ + config, + args: secretBulkArgs, + accountId, + scriptName, + }); + await submitSecrets(key, value); + // TODO: delete the draft worker if this failed too? + } return false; - }); + } + } }) );