Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version secrets + secret behaviour changes #5950

Merged
merged 1 commit into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/little-rice-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"wrangler": minor
---

feat: add `wrangler versions secret put`, `wrangler versions secret bulk` and `wrangler versions secret list`

`wrangler versions secret put` allows for you to add/update a secret even if the latest version is not fully deployed. A new version with this secret will be created, the existing secrets and config are copied from the latest version.

`wrangler versions secret bulk` allows you to bulk add/update multiple secrets at once, this behaves the same as `secret put` and will only make one new version.

`wrangler versions secret list` lists the secrets available to the currently deployed versions. `wrangler versions secret list --latest-version` or `wrangler secret list` will list for the latest version.

Additionally, we will now prompt for extra confirmation if attempting to rollback to a version with different secrets than the currently deployed.
235 changes: 235 additions & 0 deletions packages/wrangler/src/__tests__/rollback.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import { http, HttpResponse } from "msw";
import { describe, expect, test } from "vitest";
import { CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE } from "../versions/api";
import { collectCLIOutput } from "./helpers/collect-cli-output";
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
import { mockConfirm, mockPrompt } from "./helpers/mock-dialogs";
import { useMockIsTTY } from "./helpers/mock-istty";
import { createFetchResult, msw } from "./helpers/msw";
import { runWrangler } from "./helpers/run-wrangler";
import type { ApiDeployment } from "../versions/types";

describe("rollback", () => {
const std = collectCLIOutput();
const { setIsTTY } = useMockIsTTY();
mockAccountId();
mockApiToken();

function mockGetDeployments(multiVersion = false) {
const versions = multiVersion
? [
{ version_id: "version-id-1", percentage: 50 },
{ version_id: "version-id-2", percentage: 50 },
]
: [{ version_id: "version-id-1", percentage: 100 }];

msw.use(
http.get(
`*/accounts/:accountId/workers/scripts/:scriptName/deployments`,
async ({ params }) => {
expect(params.accountId).toEqual("some-account-id");
expect(params.scriptName).toEqual("script-name");

return HttpResponse.json(
createFetchResult({
deployments: [
{
id: "deployment-id",
source: "api",
strategy: "percentage",
versions,
},
] as ApiDeployment[],
})
);
},
{ once: true }
)
);
}

function mockGetVersion(versionId: string) {
msw.use(
http.get(
`*/accounts/:accountId/workers/scripts/:scriptName/versions/${versionId}`,
async ({ params }) => {
expect(params.accountId).toEqual("some-account-id");
expect(params.scriptName).toEqual("script-name");

return HttpResponse.json(
createFetchResult({
id: versionId,
metadata: {},
number: 2,
resources: {
bindings: [
{
type: "secret_text",
name: "SECRET_1",
text: "First secret",
},
{
type: "secret_text",
name: "SECRET_2",
text: "Second secret",
},
{
type: "secret_text",
name: "SECRET_3",
text: "Third secret",
},
],
script: {
etag: "etag",
handlers: ["fetch"],
last_deployed_from: "api",
},
script_runtime: {
usage_model: "standard",
limits: {},
},
},
})
);
},
{ once: true }
)
);
}

function mockPostDeployment(forced = false) {
msw.use(
http.post(
`*/accounts/:accountId/workers/scripts/:scriptName/deployments${forced ? "?force=true" : ""}`,
async ({ params }) => {
expect(params.accountId).toEqual("some-account-id");
expect(params.scriptName).toEqual("script-name");

return HttpResponse.json(createFetchResult({}));
},
{ once: true }
)
);
}

test("can rollback to an earlier version", async () => {
mockGetDeployments();
mockGetVersion("version-id-1");
mockGetVersion("rollback-version");
mockPostDeployment();

mockPrompt({
text: "Please provide a message for this rollback (120 characters max, optional)?",
result: "Test rollback",
});

mockConfirm({
text: "Are you sure you want to deploy this Worker Version to 100% of traffic?",
result: true,
});

await runWrangler(
"rollback --name script-name --version-id rollback-version --x-versions"
);

// Unable to test stdout as the output has weird whitespace. Causing lint to fail with "no-irregular-whitespace"
expect(std.err).toMatchInlineSnapshot(`""`);
});

test("rolling back with changed secrets prompts confirmation", async () => {
mockGetDeployments();
mockGetVersion("version-id-1");
mockGetVersion("rollback-version");

// Deployment will fail due to changed secret
msw.use(
http.post(
`*/accounts/:accountId/workers/scripts/:scriptName/deployments`,
async ({ params }) => {
expect(params.accountId).toEqual("some-account-id");
expect(params.scriptName).toEqual("script-name");

return HttpResponse.json(
createFetchResult(null, false, [
{
code: CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE,
message:
"A secret has changed since this version was active. If you are sure this is ok, add a ?force=true query parameter to allow the rollback. The following secrets have changed: SECRET, SECRET_TWO",
},
]),
{ status: 400 }
);
},
{ once: true }
)
);

// Now deploy again with force and succeed
mockPostDeployment(true);

mockPrompt({
text: "Please provide a message for this rollback (120 characters max, optional)?",
result: "Test rollback",
});

mockConfirm({
text: "Are you sure you want to deploy this Worker Version to 100% of traffic?",
result: true,
});

// We will have an additional confirmation
mockConfirm({
text:
"The following secrets have changed since the target version was deployed. Please confirm you wish to continue with the rollback" +
"\n * SECRET\n * SECRET_TWO",
result: true,
});

await runWrangler(
"rollback --name script-name --version-id rollback-version --x-versions"
);

// Unable to test stdout as the output has weird whitespace. Causing lint to fail with "no-irregular-whitespace"
expect(std.err).toMatchInlineSnapshot(`""`);
});

test("rolling back with changed secrets (non-interactive)", async () => {
CarmenPopoviciu marked this conversation as resolved.
Show resolved Hide resolved
setIsTTY(false);
mockGetDeployments();
mockGetVersion("version-id-1");
mockGetVersion("rollback-version");

// Deployment will fail due to changed secret
msw.use(
http.post(
`*/accounts/:accountId/workers/scripts/:scriptName/deployments`,
async ({ params }) => {
expect(params.accountId).toEqual("some-account-id");
expect(params.scriptName).toEqual("script-name");

return HttpResponse.json(
createFetchResult(null, false, [
{
code: CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE,
message:
"A secret has changed since this version was active. If you are sure this is ok, add a ?force=true query parameter to allow the rollback. The following secrets have changed: SECRET, SECRET_TWO",
},
]),
{ status: 400 }
);
},
{ once: true }
)
);

// Now deploy again with force and succeed
mockPostDeployment(true);

await runWrangler(
"rollback --name script-name --version-id rollback-version --x-versions"
);

// Unable to test stdout as the output has weird whitespace. Causing lint to fail with "no-irregular-whitespace"
expect(std.err).toMatchInlineSnapshot(`""`);
});
});
42 changes: 21 additions & 21 deletions packages/wrangler/src/__tests__/secret.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,13 +503,13 @@ describe("wrangler secret", () => {
mockListRequest({ scriptName: "script-name" });
await runWrangler("secret list --name script-name");
expect(std.out).toMatchInlineSnapshot(`
"[
{
\\"name\\": \\"the-secret-name\\",
\\"type\\": \\"secret_text\\"
}
]"
`);
"[
{
\\"name\\": \\"the-secret-name\\",
\\"type\\": \\"secret_text\\"
}
]"
`);
expect(std.err).toMatchInlineSnapshot(`""`);
});

Expand All @@ -519,13 +519,13 @@ describe("wrangler secret", () => {
"secret list --name script-name --env some-env --legacy-env"
);
expect(std.out).toMatchInlineSnapshot(`
"[
{
\\"name\\": \\"the-secret-name\\",
\\"type\\": \\"secret_text\\"
}
]"
`);
"[
{
\\"name\\": \\"the-secret-name\\",
\\"type\\": \\"secret_text\\"
}
]"
`);
expect(std.err).toMatchInlineSnapshot(`""`);
});

Expand All @@ -535,13 +535,13 @@ describe("wrangler secret", () => {
"secret list --name script-name --env some-env --legacy-env false"
);
expect(std.out).toMatchInlineSnapshot(`
"[
{
\\"name\\": \\"the-secret-name\\",
\\"type\\": \\"secret_text\\"
}
]"
`);
"[
{
\\"name\\": \\"the-secret-name\\",
\\"type\\": \\"secret_text\\"
}
]"
`);
expect(std.err).toMatchInlineSnapshot(`""`);
});

Expand Down
Loading
Loading