From 70c5af4686f4ee0010de41b09f55ef7dbd5da220 Mon Sep 17 00:00:00 2001 From: Jacob M-G Evans Date: Mon, 25 Sep 2023 12:55:06 -0500 Subject: [PATCH] Draft Worker for Bulk Secrets 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; - }); + } + } }) );