From 2278616b517e17dede77a675d5d2dc6847489f50 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Mon, 4 Nov 2024 14:21:00 +0000 Subject: [PATCH] Added pause and resume commands to manage Workflows (#7121) * Added pause and resume commands to manage Workflows * prettify check * Update packages/wrangler/src/workflows/commands/instances/pause.ts Co-authored-by: emily-shen <69125074+emily-shen@users.noreply.github.com> * Update packages/wrangler/src/workflows/commands/instances/resume.ts Co-authored-by: emily-shen <69125074+emily-shen@users.noreply.github.com> * add happy path test for `pause` * added workflows cmds tests and fix delete not being implemented * try to fix timezones * hidded workflows delete command from help as it is not yet implemented * Update packages/wrangler/src/__tests__/workflows.test.ts Co-authored-by: emily-shen <69125074+emily-shen@users.noreply.github.com> * Update packages/wrangler/src/__tests__/workflows.test.ts Co-authored-by: emily-shen <69125074+emily-shen@users.noreply.github.com> * fixed should get the list of instances given a name test * better changeset * added mocked instance id when a workflow is triggered * prettify cleanup --------- Co-authored-by: emily-shen <69125074+emily-shen@users.noreply.github.com> --- .changeset/violet-zoos-obey.md | 5 + .../wrangler/src/__tests__/workflows.test.ts | 432 ++++++++++++++++++ .../wrangler/src/workflows/commands/delete.ts | 14 +- .../src/workflows/commands/instances/list.ts | 3 +- .../src/workflows/commands/instances/pause.ts | 68 +++ .../workflows/commands/instances/resume.ts | 68 +++ packages/wrangler/src/workflows/index.ts | 2 + 7 files changed, 581 insertions(+), 11 deletions(-) create mode 100644 .changeset/violet-zoos-obey.md create mode 100644 packages/wrangler/src/__tests__/workflows.test.ts create mode 100644 packages/wrangler/src/workflows/commands/instances/pause.ts create mode 100644 packages/wrangler/src/workflows/commands/instances/resume.ts diff --git a/.changeset/violet-zoos-obey.md b/.changeset/violet-zoos-obey.md new file mode 100644 index 000000000000..d0396ce2f40b --- /dev/null +++ b/.changeset/violet-zoos-obey.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +Added pause and resume commands to manage Workflows and hidded unimplemented delete command diff --git a/packages/wrangler/src/__tests__/workflows.test.ts b/packages/wrangler/src/__tests__/workflows.test.ts new file mode 100644 index 000000000000..08bc32cb8d8b --- /dev/null +++ b/packages/wrangler/src/__tests__/workflows.test.ts @@ -0,0 +1,432 @@ +import { http, HttpResponse } from "msw"; +import { endEventLoop } from "./helpers/end-event-loop"; +import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; +import { mockConsoleMethods } from "./helpers/mock-console"; +import { clearDialogs } from "./helpers/mock-dialogs"; +import { msw } from "./helpers/msw"; +import { runInTempDir } from "./helpers/run-in-tmp"; +import { runWrangler } from "./helpers/run-wrangler"; +import { writeWranglerToml } from "./helpers/write-wrangler-toml"; +import type { Instance, Workflow } from "../workflows/types"; + +describe("wrangler workflows", () => { + const std = mockConsoleMethods(); + runInTempDir(); + mockAccountId(); + mockApiToken(); + afterEach(() => { + clearDialogs(); + }); + + const mockGetInstances = async (instances: Instance[]) => { + msw.use( + http.get( + `*/accounts/:accountId/workflows/some-workflow/instances`, + async () => { + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: instances, + }); + }, + { once: true } + ) + ); + }; + + const mockPatchRequest = async (expectedInstance: string) => { + msw.use( + http.patch( + `*/accounts/:accountId/workflows/some-workflow/instances/:instanceId/status`, + async ({ params }) => { + expect(params.instanceId).toEqual(expectedInstance); + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: {}, + }); + }, + { once: true } + ) + ); + }; + + describe("help", () => { + it("should show help when no argument is passed", async () => { + writeWranglerToml(); + + await runWrangler(`workflows`); + await endEventLoop(); + + expect(std.out).toMatchInlineSnapshot( + `"wrangler workflows + +πŸ” Manage Workflows [open-beta] + +COMMANDS + wrangler workflows list List Workflows associated to account [open-beta] + wrangler workflows describe Describe Workflow resource [open-beta] + wrangler workflows trigger [params] Trigger a workflow, creating a new instance. Can optionally take a JSON string to pass a parameter into the workflow instance [open-beta] + wrangler workflows instances Manage Workflow instances [open-beta] + +GLOBAL FLAGS + -j, --experimental-json-config Experimental: support wrangler.json [boolean] + -c, --config Path to .toml configuration file [string] + -e, --env Environment to use for operations and .env files [string] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]"` + ); + }); + }); + + describe("instances help", () => { + it("should show instance help when no argument is passed", async () => { + writeWranglerToml(); + + await runWrangler(`workflows instances`); + await endEventLoop(); + + expect(std.out).toMatchInlineSnapshot( + `"wrangler workflows instances + +Manage Workflow instances [open-beta] + +COMMANDS + wrangler workflows instances list Instance related commands (list, describe, terminate, pause, resume) [open-beta] + wrangler workflows instances describe Describe a workflow instance - see its logs, retries and errors [open-beta] + wrangler workflows instances terminate Terminate a workflow instance [open-beta] + wrangler workflows instances pause Pause a workflow instance [open-beta] + wrangler workflows instances resume Resume a workflow instance [open-beta] + +GLOBAL FLAGS + -j, --experimental-json-config Experimental: support wrangler.json [boolean] + -c, --config Path to .toml configuration file [string] + -e, --env Environment to use for operations and .env files [string] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]"` + ); + }); + }); + + describe("list", () => { + const mockWorkflows: Workflow[] = [ + { + class_name: "wf_class_1", + created_on: "2021-01-01T00:00:00Z", + id: "wf_id_1", + modified_on: "2021-01-01T00:00:00Z", + name: "wf_1", + script_name: "wf_script_1", + }, + { + class_name: "wf_class_2", + created_on: "2022-01-01T00:00:00Z", + id: "wf_id_2", + modified_on: "2022-01-01T00:00:00Z", + name: "wf_2", + script_name: "wf_script_2", + }, + ]; + + const mockGetWorkflows = async (workflows: Workflow[]) => { + msw.use( + http.get( + `*/accounts/:accountId/workflows`, + async () => { + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: workflows, + }); + }, + { once: true } + ) + ); + }; + + it("should get the list of workflows", async () => { + writeWranglerToml(); + await mockGetWorkflows(mockWorkflows); + + await runWrangler(`workflows list`); + expect(std.info).toMatchInlineSnapshot(`"Showing last 2 workflows:"`); + expect(std.out).toMatchInlineSnapshot( + ` +"β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Name β”‚ Script name β”‚ Class name β”‚ Created β”‚ Modified β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ wf_1 β”‚ wf_script_1 β”‚ wf_class_1 β”‚ 1/1/2021, 12:00:00 AM β”‚ 1/1/2021, 12:00:00 AM β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ wf_2 β”‚ wf_script_2 β”‚ wf_class_2 β”‚ 1/1/2022, 12:00:00 AM β”‚ 1/1/2022, 12:00:00 AM β”‚ +β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜" + ` + ); + }); + }); + + describe("instances list", () => { + const mockInstances: Instance[] = [ + { + id: "foo", + created_on: "2021-01-01T00:00:00Z", + modified_on: "2021-01-01T00:00:00Z", + workflow_id: "b", + version_id: "c", + status: "running", + }, + { + id: "bar", + created_on: "2022-01-01T00:00:00Z", + modified_on: "2022-01-01T00:00:00Z", + workflow_id: "b", + version_id: "c", + status: "running", + }, + ]; + + it("should get the list of instances given a name", async () => { + writeWranglerToml(); + await mockGetInstances(mockInstances); + + await runWrangler(`workflows instances list some-workflow`); + expect(std.info).toMatchInlineSnapshot( + `"Showing 2 instances from page 1:"` + ); + expect(std.out).toMatchInlineSnapshot( + ` +"β”Œβ”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Id β”‚ Version β”‚ Created β”‚ Modified β”‚ Status β”‚ +β”œβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ bar β”‚ c β”‚ 1/1/2022, 12:00:00 AM β”‚ 1/1/2022, 12:00:00 AM β”‚ β–Ά Running β”‚ +β”œβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ foo β”‚ c β”‚ 1/1/2021, 12:00:00 AM β”‚ 1/1/2021, 12:00:00 AM β”‚ β–Ά Running β”‚ +β””β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜" + ` + ); + }); + }); + + describe("instances describe", () => { + const mockDescribeInstances = async () => { + const mockResponse = { + end: "2021-01-01T00:00:00Z", + output: "string", + params: {}, + queued: "2021-01-01T00:00:00Z", + start: "2021-01-01T00:00:00Z", + status: "queued", + success: true, + trigger: { + source: "unknown", + }, + versionId: "14707576-2549-4848-82ed-f68f8a1b47c7", + steps: [ + { + attempts: [ + { + end: "2021-01-01T00:00:00Z", + error: { + message: "string", + name: "string", + }, + start: "2021-01-01T00:00:00Z", + success: true, + }, + ], + config: { + retries: { + backoff: "constant", + delay: "string", + limit: 0, + }, + timeout: "string", + }, + end: "2021-01-01T00:00:00Z", + name: "string", + output: {}, + start: "2021-01-01T00:00:00Z", + success: true, + type: "step", + }, + ], + }; + + msw.use( + http.get( + `*/accounts/:accountId/workflows/some-workflow/instances/:instanceId`, + async () => { + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: mockResponse, + }); + }, + { once: true } + ) + ); + }; + + it("should describe the bar instance given a name", async () => { + writeWranglerToml(); + await mockDescribeInstances(); + + await runWrangler(`workflows instances describe some-workflow bar`); + expect(std.out).toMatchInlineSnapshot(` +"β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Start β”‚ End β”‚ Duration β”‚ State β”‚ Error β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 1/1/2021, 12:00:00 AM β”‚ 1/1/2021, 12:00:00 AM β”‚ 0 seconds β”‚ βœ… Success β”‚ string: string β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜" + `); + }); + }); + + describe("instances pause", () => { + const mockInstances: Instance[] = [ + { + id: "foo", + created_on: "2021-01-01T00:00:00Z", + modified_on: "2021-01-01T00:00:00Z", + workflow_id: "b", + version_id: "c", + status: "running", + }, + { + id: "bar", + created_on: "2022-01-01T00:00:00Z", + modified_on: "2022-01-01T00:00:00Z", + workflow_id: "b", + version_id: "c", + status: "running", + }, + ]; + + it("should get and pause the bar instance given a name", async () => { + writeWranglerToml(); + await mockGetInstances(mockInstances); + await mockPatchRequest("bar"); + + await runWrangler(`workflows instances pause some-workflow bar`); + expect(std.info).toMatchInlineSnapshot( + `"⏸️ The instance \\"bar\\" from some-workflow was paused successfully"` + ); + }); + }); + + describe("instances resume", () => { + const mockInstances: Instance[] = [ + { + id: "foo", + created_on: "2021-01-01T00:00:00Z", + modified_on: "2021-01-01T00:00:00Z", + workflow_id: "b", + version_id: "c", + status: "running", + }, + { + id: "bar", + created_on: "2022-01-01T00:00:00Z", + modified_on: "2022-01-01T00:00:00Z", + workflow_id: "b", + version_id: "c", + status: "paused", + }, + ]; + + it("should get and resume the bar instance given a name", async () => { + writeWranglerToml(); + await mockGetInstances(mockInstances); + await mockPatchRequest("bar"); + + await runWrangler(`workflows instances resume some-workflow bar`); + expect(std.info).toMatchInlineSnapshot( + `"πŸ”„ The instance \\"bar\\" from some-workflow was resumed successfully"` + ); + }); + }); + + describe("instances terminate", () => { + const mockInstances: Instance[] = [ + { + id: "foo", + created_on: "2021-01-01T00:00:00Z", + modified_on: "2021-01-01T00:00:00Z", + workflow_id: "b", + version_id: "c", + status: "running", + }, + { + id: "bar", + created_on: "2022-01-01T00:00:00Z", + modified_on: "2022-01-01T00:00:00Z", + workflow_id: "b", + version_id: "c", + status: "running", + }, + ]; + + it("should get and terminate the bar instance given a name", async () => { + writeWranglerToml(); + await mockGetInstances(mockInstances); + await mockPatchRequest("bar"); + + await runWrangler(`workflows instances terminate some-workflow bar`); + expect(std.info).toMatchInlineSnapshot( + `"πŸ₯· The instance \\"bar\\" from some-workflow was terminated successfully"` + ); + }); + }); + + describe("trigger", () => { + const mockTriggerWorkflow = async () => { + msw.use( + http.post( + `*/accounts/:accountId/workflows/some-workflow/instances`, + async () => { + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: { + id: "3c70754a-8435-4498-92ad-22e2e2c90853", + status: "queued", + version_id: "9e94c502-ca41-4342-a7f7-af96b444512c", + workflow_id: "03e70e31-d7a4-4401-a629-6a4b6096cdfe", + }, + }); + }, + { once: true } + ) + ); + }; + + it("should trigger a workflow given a name", async () => { + writeWranglerToml(); + await mockTriggerWorkflow(); + + await runWrangler(`workflows trigger some-workflow`); + expect(std); + expect(std.info).toMatchInlineSnapshot( + `"πŸš€ Workflow instance \\"3c70754a-8435-4498-92ad-22e2e2c90853\\" has been queued successfully"` + ); + }); + }); + + describe("delete", () => { + it("should delete a workflow - check not implemented", async () => { + writeWranglerToml(); + + await runWrangler(`workflows delete some-workflow`); + expect(std.out).toMatchInlineSnapshot( + `"🚫 Workflow \\"some-workflow\\" NOT removed"` + ); + expect(std.info).toMatchInlineSnapshot( + `"🚫 delete command not yet implement"` + ); + }); + }); +}); diff --git a/packages/wrangler/src/workflows/commands/delete.ts b/packages/wrangler/src/workflows/commands/delete.ts index c5b1e00aa7c9..5d6ea8e9da38 100644 --- a/packages/wrangler/src/workflows/commands/delete.ts +++ b/packages/wrangler/src/workflows/commands/delete.ts @@ -1,7 +1,5 @@ -import { fetchResult } from "../../cfetch"; import { defineCommand } from "../../core"; import { logger } from "../../logger"; -import { requireAuth } from "../../user"; defineCommand({ command: "wrangler workflows delete", @@ -10,6 +8,7 @@ defineCommand({ "Delete workflow - when deleting a workflow, it will also delete it's own instances", owner: "Product: Workflows", status: "open-beta", + hidden: true, }, args: { @@ -21,13 +20,8 @@ defineCommand({ }, positionalArgs: ["name"], - async handler(args, { config }) { - const accountId = await requireAuth(config); - - await fetchResult(`/accounts/${accountId}/workflows/${args.name}`, { - method: "DELETE", - }); - - logger.info(`Workflow "${args.name}" was successfully removed`); + async handler(args) { + logger.info("🚫 delete command not yet implement"); + logger.log(`🚫 Workflow "${args.name}" NOT removed`); }, }); diff --git a/packages/wrangler/src/workflows/commands/instances/list.ts b/packages/wrangler/src/workflows/commands/instances/list.ts index 8ad1166d41c7..0b8ee7dea143 100644 --- a/packages/wrangler/src/workflows/commands/instances/list.ts +++ b/packages/wrangler/src/workflows/commands/instances/list.ts @@ -9,7 +9,8 @@ defineCommand({ command: "wrangler workflows instances list", metadata: { - description: "Instance related commands (list, describe, terminate...)", + description: + "Instance related commands (list, describe, terminate, pause, resume)", owner: "Product: Workflows", status: "open-beta", }, diff --git a/packages/wrangler/src/workflows/commands/instances/pause.ts b/packages/wrangler/src/workflows/commands/instances/pause.ts new file mode 100644 index 000000000000..4c9bb05ebe21 --- /dev/null +++ b/packages/wrangler/src/workflows/commands/instances/pause.ts @@ -0,0 +1,68 @@ +import { fetchResult } from "../../../cfetch"; +import { defineCommand } from "../../../core"; +import { logger } from "../../../logger"; +import { requireAuth } from "../../../user"; +import type { Instance } from "../../types"; + +defineCommand({ + command: "wrangler workflows instances pause", + + metadata: { + description: "Pause a workflow instance", + owner: "Product: Workflows", + status: "open-beta", + }, + + positionalArgs: ["name", "id"], + args: { + name: { + describe: "Name of the workflow", + type: "string", + demandOption: true, + }, + id: { + describe: + "ID of the instance - instead of an UUID you can type 'latest' to get the latest instance and pause it", + type: "string", + demandOption: true, + }, + }, + + async handler(args, { config }) { + const accountId = await requireAuth(config); + + let id = args.id; + + if (id == "latest") { + const instances = ( + await fetchResult( + `/accounts/${accountId}/workflows/${args.name}/instances` + ) + ).sort((a, b) => b.created_on.localeCompare(a.created_on)); + + if (instances.length == 0) { + logger.error( + `There are no deployed instances in workflow "${args.name}"` + ); + return; + } + + id = instances[0].id; + } + + await fetchResult( + `/accounts/${accountId}/workflows/${args.name}/instances/${id}/status`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "pause" }), + } + ); + + logger.info( + `⏸️ The instance "${id}" from ${args.name} was paused successfully` + ); + }, +}); diff --git a/packages/wrangler/src/workflows/commands/instances/resume.ts b/packages/wrangler/src/workflows/commands/instances/resume.ts new file mode 100644 index 000000000000..31b003617603 --- /dev/null +++ b/packages/wrangler/src/workflows/commands/instances/resume.ts @@ -0,0 +1,68 @@ +import { fetchResult } from "../../../cfetch"; +import { defineCommand } from "../../../core"; +import { logger } from "../../../logger"; +import { requireAuth } from "../../../user"; +import type { Instance } from "../../types"; + +defineCommand({ + command: "wrangler workflows instances resume", + + metadata: { + description: "Resume a workflow instance", + owner: "Product: Workflows", + status: "open-beta", + }, + + positionalArgs: ["name", "id"], + args: { + name: { + describe: "Name of the workflow", + type: "string", + demandOption: true, + }, + id: { + describe: + "ID of the instance - instead of an UUID you can type 'latest' to get the latest instance and resume it", + type: "string", + demandOption: true, + }, + }, + + async handler(args, { config }) { + const accountId = await requireAuth(config); + + let id = args.id; + + if (id == "latest") { + const instances = ( + await fetchResult( + `/accounts/${accountId}/workflows/${args.name}/instances` + ) + ).sort((a, b) => b.created_on.localeCompare(a.created_on)); + + if (instances.length == 0) { + logger.error( + `There are no deployed instances in workflow "${args.name}"` + ); + return; + } + + id = instances[0].id; + } + + await fetchResult( + `/accounts/${accountId}/workflows/${args.name}/instances/${id}/status`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "resume" }), + } + ); + + logger.info( + `πŸ”„ The instance "${id}" from ${args.name} was resumed successfully` + ); + }, +}); diff --git a/packages/wrangler/src/workflows/index.ts b/packages/wrangler/src/workflows/index.ts index 5a20e510d89c..f604a360cd94 100644 --- a/packages/wrangler/src/workflows/index.ts +++ b/packages/wrangler/src/workflows/index.ts @@ -6,6 +6,8 @@ import "./commands/trigger"; import "./commands/instances/list"; import "./commands/instances/describe"; import "./commands/instances/terminate"; +import "./commands/instances/pause"; +import "./commands/instances/resume"; defineNamespace({ command: "wrangler workflows",