Skip to content

Commit d538973

Browse files
authored
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
1 parent 3cd7286 commit d538973

File tree

3 files changed

+132
-91
lines changed

3 files changed

+132
-91
lines changed

.changeset/pretty-avocados-worry.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
fix: Bulk Secret Draft Worker
6+
7+
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.
8+
9+
Fixes https://github.com/cloudflare/wrangler-action/issues/162

packages/wrangler/src/__tests__/secret.test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -678,28 +678,28 @@ describe("wrangler secret", () => {
678678
expect(std.err).toMatchInlineSnapshot(`
679679
"X [ERROR] uploading secret for key: secret-name-1:
680680
681-
request to
681+
request to
682682
https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets
683683
failed, reason: Failed to create secret 1
684684
685685
686686
X [ERROR] uploading secret for key: secret-name-3:
687687
688-
request to
688+
request to
689689
https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets
690690
failed, reason: Failed to create secret 3
691691
692692
693693
X [ERROR] uploading secret for key: secret-name-5:
694694
695-
request to
695+
request to
696696
https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets
697697
failed, reason: Failed to create secret 5
698698
699699
700700
X [ERROR] uploading secret for key: secret-name-7:
701701
702-
request to
702+
request to
703703
https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets
704704
failed, reason: Failed to create secret 7
705705
@@ -750,14 +750,14 @@ describe("wrangler secret", () => {
750750
expect(std.err).toMatchInlineSnapshot(`
751751
"X [ERROR] uploading secret for key: secret-name-1:
752752
753-
request to
753+
request to
754754
https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets
755755
failed, reason: Failed to create secret 1
756756
757757
758758
X [ERROR] uploading secret for key: secret-name-2:
759759
760-
request to
760+
request to
761761
https://api.cloudflare.com/client/v4/accounts/some-account-id/workers/scripts/script-name/secrets
762762
failed, reason: Failed to create secret 2
763763

packages/wrangler/src/secret/index.ts

+117-85
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,86 @@ import * as metrics from "../metrics";
1414
import { parseJSON, readFileSync } from "../parse";
1515
import { requireAuth } from "../user";
1616

17+
import type { Config } from "../config";
1718
import type {
1819
CommonYargsArgv,
1920
StrictYargsOptionsToInterface,
2021
} from "../yargs-types";
2122

23+
function isMissingWorkerError(e: unknown): e is { code: 10007 } {
24+
return (
25+
typeof e === "object" &&
26+
e !== null &&
27+
(e as { code: number }).code === 10007
28+
);
29+
}
30+
31+
async function createDraftWorker({
32+
config,
33+
args,
34+
accountId,
35+
scriptName,
36+
}: {
37+
config: Config;
38+
args: { env?: string; name?: string };
39+
accountId: string;
40+
scriptName: string;
41+
}) {
42+
// TODO: log a warning
43+
await fetchResult(
44+
!isLegacyEnv(config) && args.env
45+
? `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}`
46+
: `/accounts/${accountId}/workers/scripts/${scriptName}`,
47+
{
48+
method: "PUT",
49+
body: createWorkerUploadForm({
50+
name: scriptName,
51+
main: {
52+
name: scriptName,
53+
filePath: undefined,
54+
content: `export default { fetch() {} }`,
55+
type: "esm",
56+
},
57+
bindings: {
58+
kv_namespaces: [],
59+
send_email: [],
60+
vars: {},
61+
durable_objects: { bindings: [] },
62+
queues: [],
63+
r2_buckets: [],
64+
d1_databases: [],
65+
vectorize: [],
66+
constellation: [],
67+
services: [],
68+
analytics_engine_datasets: [],
69+
wasm_modules: {},
70+
browser: undefined,
71+
ai: undefined,
72+
text_blobs: {},
73+
data_blobs: {},
74+
dispatch_namespaces: [],
75+
mtls_certificates: [],
76+
logfwdr: { bindings: [] },
77+
unsafe: {
78+
bindings: undefined,
79+
metadata: undefined,
80+
capnp: undefined,
81+
},
82+
},
83+
modules: [],
84+
migrations: undefined,
85+
compatibility_date: undefined,
86+
compatibility_flags: undefined,
87+
usage_model: undefined,
88+
keepVars: false, // this doesn't matter since it's a new script anyway
89+
logpush: false,
90+
placement: undefined,
91+
tail_consumers: undefined,
92+
}),
93+
}
94+
);
95+
}
96+
2297
export const secret = (secretYargs: CommonYargsArgv) => {
2398
return secretYargs
2499
.option("legacy-env", {
@@ -84,70 +159,6 @@ export const secret = (secretYargs: CommonYargsArgv) => {
84159
});
85160
}
86161

87-
const createDraftWorker = async () => {
88-
// TODO: log a warning
89-
await fetchResult(
90-
!isLegacyEnv(config) && args.env
91-
? `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}`
92-
: `/accounts/${accountId}/workers/scripts/${scriptName}`,
93-
{
94-
method: "PUT",
95-
body: createWorkerUploadForm({
96-
name: scriptName,
97-
main: {
98-
name: scriptName,
99-
filePath: undefined,
100-
content: `export default { fetch() {} }`,
101-
type: "esm",
102-
},
103-
bindings: {
104-
kv_namespaces: [],
105-
send_email: [],
106-
vars: {},
107-
durable_objects: { bindings: [] },
108-
queues: [],
109-
r2_buckets: [],
110-
d1_databases: [],
111-
vectorize: [],
112-
constellation: [],
113-
services: [],
114-
analytics_engine_datasets: [],
115-
wasm_modules: {},
116-
browser: undefined,
117-
ai: undefined,
118-
text_blobs: {},
119-
data_blobs: {},
120-
dispatch_namespaces: [],
121-
mtls_certificates: [],
122-
logfwdr: { bindings: [] },
123-
unsafe: {
124-
bindings: undefined,
125-
metadata: undefined,
126-
capnp: undefined,
127-
},
128-
},
129-
modules: [],
130-
migrations: undefined,
131-
compatibility_date: undefined,
132-
compatibility_flags: undefined,
133-
usage_model: undefined,
134-
keepVars: false, // this doesn't matter since it's a new script anyway
135-
logpush: false,
136-
placement: undefined,
137-
tail_consumers: undefined,
138-
}),
139-
}
140-
);
141-
};
142-
143-
function isMissingWorkerError(e: unknown): e is { code: 10007 } {
144-
return (
145-
typeof e === "object" &&
146-
e !== null &&
147-
(e as { code: number }).code === 10007
148-
);
149-
}
150-
151162
try {
152163
await submitSecret();
153164
await metrics.sendMetricsEvent("create encrypted variable", {
@@ -156,7 +167,12 @@ export const secret = (secretYargs: CommonYargsArgv) => {
156167
} catch (e) {
157168
if (isMissingWorkerError(e)) {
158169
// create a draft worker and try again
159-
await createDraftWorker();
170+
await createDraftWorker({
171+
config,
172+
args,
173+
accountId,
174+
scriptName,
175+
});
160176
await submitSecret();
161177
// TODO: delete the draft worker if this failed too?
162178
} else {
@@ -363,33 +379,49 @@ export const secretBulkHandler = async (secretBulkArgs: SecretBulkArgs) => {
363379
return logger.error(`🚨 No content found in JSON file or piped input.`);
364380
}
365381

366-
const url =
367-
!secretBulkArgs.env || isLegacyEnv(config)
368-
? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
369-
: `/accounts/${accountId}/workers/services/${scriptName}/environments/${secretBulkArgs.env}/secrets`;
382+
function submitSecrets(key: string, value: string) {
383+
const url =
384+
!secretBulkArgs.env || isLegacyEnv(config)
385+
? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
386+
: `/accounts/${accountId}/workers/services/${scriptName}/environments/${secretBulkArgs.env}/secrets`;
387+
388+
return fetchResult(url, {
389+
method: "PUT",
390+
headers: { "Content-Type": "application/json" },
391+
body: JSON.stringify({
392+
name: key,
393+
text: value,
394+
type: "secret_text",
395+
}),
396+
});
397+
}
370398
// Until we have a bulk route for secrets, we need to make a request for each key/value pair
371399
const bulkOutcomes = await Promise.all(
372400
Object.entries(content).map(async ([key, value]) => {
373-
return fetchResult(url, {
374-
method: "PUT",
375-
headers: { "Content-Type": "application/json" },
376-
body: JSON.stringify({
377-
name: key,
378-
text: value,
379-
type: "secret_text",
380-
}),
381-
})
382-
.then(() => {
383-
logger.log(`✨ Successfully created secret for key: ${key}`);
384-
return true;
385-
})
386-
.catch((e) => {
401+
try {
402+
await submitSecrets(key, value);
403+
logger.log(`✨ Successfully created secret for key: ${key}`);
404+
return true;
405+
} catch (e) {
406+
if (e instanceof Error) {
387407
logger.error(
388408
`uploading secret for key: ${key}:
389-
${e.message}`
409+
${e.message}`
390410
);
411+
if (isMissingWorkerError(e)) {
412+
// create a draft worker and try again
413+
await createDraftWorker({
414+
config,
415+
args: secretBulkArgs,
416+
accountId,
417+
scriptName,
418+
});
419+
await submitSecrets(key, value);
420+
// TODO: delete the draft worker if this failed too?
421+
}
391422
return false;
392-
});
423+
}
424+
}
393425
})
394426
);
395427

0 commit comments

Comments
 (0)