diff --git a/.changeset/early-melons-deny.md b/.changeset/early-melons-deny.md new file mode 100644 index 000000000000..7dc0320b4e65 --- /dev/null +++ b/.changeset/early-melons-deny.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +Add a new `update` subcommand for Queues to allow updating Queue settings diff --git a/packages/wrangler/src/__tests__/queues.test.ts b/packages/wrangler/src/__tests__/queues.test.ts index 6495b85cdf27..627fee631049 100644 --- a/packages/wrangler/src/__tests__/queues.test.ts +++ b/packages/wrangler/src/__tests__/queues.test.ts @@ -29,6 +29,7 @@ describe("wrangler", () => { COMMANDS wrangler queues list List Queues wrangler queues create Create a Queue + wrangler queues update Update a Queue wrangler queues delete Delete a Queue wrangler queues info Get Queue information wrangler queues consumer Configure Queue consumers @@ -413,6 +414,217 @@ describe("wrangler", () => { expect(requests.count).toEqual(0); }); }); + + describe("update", () => { + function mockUpdateRequest( + queueName: string, + queueSettings: + | { delivery_delay?: number; message_retention_period?: number } + | undefined = undefined + ) { + const requests = { count: 0 }; + + msw.use( + http.patch( + "*/accounts/:accountId/queues/:queueId", + async ({ request }) => { + requests.count += 1; + + const body = (await request.json()) as { + queue_name: string; + settings: { + delivery_delay: number; + message_retention_period: number; + }; + }; + expect(body.queue_name).toEqual(queueName); + expect(body.settings).toEqual(queueSettings); + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: { + queue_name: queueName, + created_on: "01-01-2001", + modified_on: "01-01-2001", + }, + }); + }, + { once: true } + ) + ); + return requests; + } + function mockGetQueueRequest( + queueName: string, + queueSettings: { + delivery_delay: number; + message_retention_period: number; + } + ) { + const requests = { count: 0 }; + msw.use( + http.get( + "*/accounts/:accountId/queues?*", + async () => { + requests.count += 1; + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: [ + { + queue_name: queueName, + created_on: "", + producers: [], + consumers: [], + producers_total_count: 1, + consumers_total_count: 0, + modified_on: "", + queue_id: "queueId", + settings: { + delivery_delay: queueSettings.delivery_delay, + message_retention_period: + queueSettings.message_retention_period, + }, + }, + ], + }); + }, + { once: true } + ) + ); + return requests; + } + + it("should show the correct help text", async () => { + await runWrangler("queues update --help"); + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toMatchInlineSnapshot(` + "wrangler queues update + + Update a Queue + + POSITIONALS + name The name of the queue [string] [required] + + GLOBAL FLAGS + -c, --config Path to Wrangler configuration file [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] + + OPTIONS + --delivery-delay-secs How long a published message should be delayed for, in seconds. Must be between 0 and 42300 [number] + --message-retention-period-secs How long to retain a message in the queue, in seconds. Must be between 60 and 1209600 [number]" + `); + }); + + it("should update a queue with new message retention period and preserve old delivery delay", async () => { + const getrequests = mockGetQueueRequest("testQueue", { + delivery_delay: 10, + message_retention_period: 100, + }); + + //update queue with new message retention period + const requests = mockUpdateRequest("testQueue", { + delivery_delay: 10, + message_retention_period: 400, + }); + await runWrangler( + "queues update testQueue --message-retention-period-secs=400" + ); + + expect(requests.count).toEqual(1); + expect(getrequests.count).toEqual(1); + + expect(std.out).toMatchInlineSnapshot(` + "Updating queue testQueue. + Updated queue testQueue." + `); + }); + + it("should show an error when two message retention periods are set", async () => { + const requests = mockUpdateRequest("testQueue", { + message_retention_period: 60, + }); + + mockGetQueueRequest("testQueue", { + delivery_delay: 0, + message_retention_period: 100, + }); + + await expect( + runWrangler( + "queues update testQueue --message-retention-period-secs=70 --message-retention-period-secs=80" + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Cannot specify --message-retention-period-secs multiple times]` + ); + + expect(requests.count).toEqual(0); + }); + + it("should show an error when two delivery delays are set", async () => { + const requests = mockUpdateRequest("testQueue", { + delivery_delay: 10, + }); + + mockGetQueueRequest("testQueue", { + delivery_delay: 0, + message_retention_period: 100, + }); + + await expect( + runWrangler( + "queues update testQueue --delivery-delay-secs=5 --delivery-delay-secs=10" + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Cannot specify --delivery-delay-secs multiple times]` + ); + + expect(requests.count).toEqual(0); + }); + + it("should show an error when invalid delivery delay is set", async () => { + const requests = mockUpdateRequest("testQueue", { + delivery_delay: 10, + }); + + mockGetQueueRequest("testQueue", { + delivery_delay: 0, + message_retention_period: 100, + }); + + await expect( + runWrangler("queues update testQueue --delivery-delay-secs=99999") + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Invalid --delivery-delay-secs value: 99999. Must be between 0 and 43200]` + ); + + expect(requests.count).toEqual(0); + }); + + it("should show an error when invalid message retention period is set", async () => { + const requests = mockUpdateRequest("testQueue", { + message_retention_period: 100, + }); + + mockGetQueueRequest("testQueue", { + delivery_delay: 0, + message_retention_period: 100, + }); + + await expect( + runWrangler( + "queues update testQueue --message-retention-period-secs=0" + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Invalid --message-retention-period-secs value: 0. Must be between 60 and 1209600]` + ); + + expect(requests.count).toEqual(0); + }); }); describe("delete", () => { diff --git a/packages/wrangler/src/queues/cli/commands/index.ts b/packages/wrangler/src/queues/cli/commands/index.ts index 0383fff80f56..5d84c45528ed 100644 --- a/packages/wrangler/src/queues/cli/commands/index.ts +++ b/packages/wrangler/src/queues/cli/commands/index.ts @@ -4,6 +4,7 @@ import { handler as createHandler, options as createOptions } from "./create"; import { handler as deleteHandler, options as deleteOptions } from "./delete"; import { handler as infoHandler, options as infoOptions } from "./info"; import { handler as listHandler, options as listOptions } from "./list"; +import { handler as updateHandler, options as updateOptions } from "./update"; import type { CommonYargsArgv } from "../../../yargs-types"; export function queues(yargs: CommonYargsArgv) { @@ -16,6 +17,13 @@ export function queues(yargs: CommonYargsArgv) { createHandler ); + yargs.command( + "update ", + "Update a Queue", + updateOptions, + updateHandler + ); + yargs.command( "delete ", "Delete a Queue", diff --git a/packages/wrangler/src/queues/cli/commands/update.ts b/packages/wrangler/src/queues/cli/commands/update.ts new file mode 100644 index 000000000000..6f35117a2e28 --- /dev/null +++ b/packages/wrangler/src/queues/cli/commands/update.ts @@ -0,0 +1,110 @@ +import { readConfig } from "../../../config"; +import { CommandLineArgsError } from "../../../errors"; +import { logger } from "../../../logger"; +import { getQueue, updateQueue } from "../../client"; +import { + MAX_DELIVERY_DELAY_SECS, + MAX_MESSAGE_RETENTION_PERIOD_SECS, + MIN_DELIVERY_DELAY_SECS, + MIN_MESSAGE_RETENTION_PERIOD_SECS, +} from "../../constants"; +import { handleFetchError } from "../../utils"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../../../yargs-types"; +import type { PostQueueBody, QueueSettings } from "../../client"; + +export function options(yargs: CommonYargsArgv) { + return yargs + .positional("name", { + type: "string", + demandOption: true, + description: "The name of the queue", + }) + .options({ + "delivery-delay-secs": { + type: "number", + describe: + "How long a published message should be delayed for, in seconds. Must be between 0 and 42300", + }, + "message-retention-period-secs": { + type: "number", + describe: + "How long to retain a message in the queue, in seconds. Must be between 60 and 1209600", + }, + }); +} + +function updateBody( + args: StrictYargsOptionsToInterface, + currentSettings?: QueueSettings +): PostQueueBody { + const body: PostQueueBody = { + queue_name: args.name, + }; + + if (Array.isArray(args.deliveryDelaySecs)) { + throw new CommandLineArgsError( + "Cannot specify --delivery-delay-secs multiple times" + ); + } + + if (Array.isArray(args.messageRetentionPeriodSecs)) { + throw new CommandLineArgsError( + "Cannot specify --message-retention-period-secs multiple times" + ); + } + + body.settings = {}; + + if (args.deliveryDelaySecs != undefined) { + if ( + args.deliveryDelaySecs < MIN_DELIVERY_DELAY_SECS || + args.deliveryDelaySecs > MAX_DELIVERY_DELAY_SECS + ) { + throw new CommandLineArgsError( + `Invalid --delivery-delay-secs value: ${args.deliveryDelaySecs}. Must be between ${MIN_DELIVERY_DELAY_SECS} and ${MAX_DELIVERY_DELAY_SECS}` + ); + } + body.settings.delivery_delay = args.deliveryDelaySecs; + } else if (currentSettings?.delivery_delay != undefined) { + body.settings.delivery_delay = currentSettings.delivery_delay; + } + + if (args.messageRetentionPeriodSecs != undefined) { + if ( + args.messageRetentionPeriodSecs < MIN_MESSAGE_RETENTION_PERIOD_SECS || + args.messageRetentionPeriodSecs > MAX_MESSAGE_RETENTION_PERIOD_SECS + ) { + throw new CommandLineArgsError( + `Invalid --message-retention-period-secs value: ${args.messageRetentionPeriodSecs}. Must be between ${MIN_MESSAGE_RETENTION_PERIOD_SECS} and ${MAX_MESSAGE_RETENTION_PERIOD_SECS}` + ); + } + body.settings.message_retention_period = args.messageRetentionPeriodSecs; + } else if (currentSettings?.message_retention_period != undefined) { + body.settings.message_retention_period = + currentSettings.message_retention_period; + } + + if (Object.keys(body.settings).length === 0) { + body.settings = undefined; + } + + return body; +} + +export async function handler( + args: StrictYargsOptionsToInterface +) { + const config = readConfig(args); + try { + const currentQueue = await getQueue(config, args.name); + const body = updateBody(args, currentQueue.settings); + logger.log(`Updating queue ${args.name}.`); + await updateQueue(config, body, currentQueue.queue_id); + logger.log(`Updated queue ${args.name}.`); + } catch (e) { + handleFetchError(e as { code?: number }); + } +} diff --git a/packages/wrangler/src/queues/client.ts b/packages/wrangler/src/queues/client.ts index 18ce0a7106dd..0608953895d0 100644 --- a/packages/wrangler/src/queues/client.ts +++ b/packages/wrangler/src/queues/client.ts @@ -115,6 +115,18 @@ export async function createQueue( }); } +export async function updateQueue( + config: Config, + body: PostQueueBody, + queue_id: string +): Promise { + const accountId = await requireAuth(config); + return fetchResult(queuesUrl(accountId, queue_id), { + method: "PATCH", + body: JSON.stringify(body), + }); +} + export async function deleteQueue( config: Config, queueName: string