diff --git a/.changeset/plenty-garlics-jump.md b/.changeset/plenty-garlics-jump.md new file mode 100644 index 000000000000..3577e0f0ab68 --- /dev/null +++ b/.changeset/plenty-garlics-jump.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +feature: allowing users to specify a description when creating an event notification rule diff --git a/packages/wrangler/src/__tests__/r2.test.ts b/packages/wrangler/src/__tests__/r2.test.ts index 4bad79554958..438bb7b95d82 100644 --- a/packages/wrangler/src/__tests__/r2.test.ts +++ b/packages/wrangler/src/__tests__/r2.test.ts @@ -1001,6 +1001,258 @@ describe("r2", () => { `); }); + it("follows happy path as expected with prefix", async () => { + const eventTypes: R2EventType[] = ["object-create", "object-delete"]; + const actions: R2EventableOperation[] = []; + const bucketName = "my-bucket"; + const queue = "my-queue"; + + const config: PutNotificationRequestBody = { + rules: [ + { + actions: eventTypes.reduce( + (acc, et) => acc.concat(actionsForEventCategories[et]), + actions + ), + prefix: "ruleprefix", + }, + ], + }; + msw.use( + http.put( + "*/accounts/:accountId/event_notifications/r2/:bucketName/configuration/queues/:queueUUID", + async ({ request, params }) => { + const { accountId } = params; + expect(accountId).toEqual("some-account-id"); + expect(await request.json()).toEqual({ + ...config, + // We fill in `prefix` & `suffix` with empty strings if not + // provided + rules: [{ ...config.rules[0], suffix: "" }], + }); + expect(request.headers.get("authorization")).toEqual( + "Bearer some-api-token" + ); + return HttpResponse.json(createFetchResult({})); + }, + { once: true } + ), + http.get( + "*/accounts/:accountId/queues?*", + async ({ request, params }) => { + const url = new URL(request.url); + const { accountId } = params; + const nameParams = url.searchParams.getAll("name"); + + expect(accountId).toEqual("some-account-id"); + expect(nameParams[0]).toEqual(queue); + expect(request.headers.get("authorization")).toEqual( + "Bearer some-api-token" + ); + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: [ + { + queue_id: "queue-id", + queue_name: queue, + created_on: "", + producers: [], + consumers: [], + producers_total_count: 1, + consumers_total_count: 0, + modified_on: "", + }, + ], + }); + }, + { once: true } + ) + ); + await expect( + runWrangler( + `r2 bucket notification create ${bucketName} --queue ${queue} --event-types ${eventTypes.join( + " " + )} --prefix "ruleprefix"` + ) + ).resolves.toBe(undefined); + expect(std.out).toMatchInlineSnapshot(` + "Creating event notification rule for object creation and deletion (PutObject,CompleteMultipartUpload,CopyObject,DeleteObject,LifecycleDeletion) + Event notification rule created successfully!" + `); + }); + + it("follows happy path as expected with suffix", async () => { + const eventTypes: R2EventType[] = ["object-create", "object-delete"]; + const actions: R2EventableOperation[] = []; + const bucketName = "my-bucket"; + const queue = "my-queue"; + + const config: PutNotificationRequestBody = { + rules: [ + { + actions: eventTypes.reduce( + (acc, et) => acc.concat(actionsForEventCategories[et]), + actions + ), + suffix: "rulesuffix", + }, + ], + }; + msw.use( + http.put( + "*/accounts/:accountId/event_notifications/r2/:bucketName/configuration/queues/:queueUUID", + async ({ request, params }) => { + const { accountId } = params; + expect(accountId).toEqual("some-account-id"); + expect(await request.json()).toEqual({ + ...config, + // We fill in `prefix` & `suffix` with empty strings if not + // provided + rules: [{ ...config.rules[0], prefix: "" }], + }); + expect(request.headers.get("authorization")).toEqual( + "Bearer some-api-token" + ); + return HttpResponse.json(createFetchResult({})); + }, + { once: true } + ), + http.get( + "*/accounts/:accountId/queues?*", + async ({ request, params }) => { + const url = new URL(request.url); + const { accountId } = params; + const nameParams = url.searchParams.getAll("name"); + + expect(accountId).toEqual("some-account-id"); + expect(nameParams[0]).toEqual(queue); + expect(request.headers.get("authorization")).toEqual( + "Bearer some-api-token" + ); + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: [ + { + queue_id: "queue-id", + queue_name: queue, + created_on: "", + producers: [], + consumers: [], + producers_total_count: 1, + consumers_total_count: 0, + modified_on: "", + }, + ], + }); + }, + { once: true } + ) + ); + await expect( + runWrangler( + `r2 bucket notification create ${bucketName} --queue ${queue} --event-types ${eventTypes.join( + " " + )} --suffix "rulesuffix"` + ) + ).resolves.toBe(undefined); + expect(std.out).toMatchInlineSnapshot(` + "Creating event notification rule for object creation and deletion (PutObject,CompleteMultipartUpload,CopyObject,DeleteObject,LifecycleDeletion) + Event notification rule created successfully!" + `); + }); + + it("follows happy path as expected with description", async () => { + const eventTypes: R2EventType[] = ["object-create", "object-delete"]; + const actions: R2EventableOperation[] = []; + const bucketName = "my-bucket"; + const queue = "my-queue"; + + const config: PutNotificationRequestBody = { + rules: [ + { + actions: eventTypes.reduce( + (acc, et) => acc.concat(actionsForEventCategories[et]), + actions + ), + description: "rule description", + }, + ], + }; + msw.use( + http.put( + "*/accounts/:accountId/event_notifications/r2/:bucketName/configuration/queues/:queueUUID", + async ({ request, params }) => { + const { accountId } = params; + expect(accountId).toEqual("some-account-id"); + expect(await request.json()).toEqual({ + ...config, + // We fill in `prefix` & `suffix` with empty strings if not + // provided + rules: [ + { + ...config.rules[0], + prefix: "", + suffix: "", + }, + ], + }); + expect(request.headers.get("authorization")).toEqual( + "Bearer some-api-token" + ); + return HttpResponse.json(createFetchResult({})); + }, + { once: true } + ), + http.get( + "*/accounts/:accountId/queues?*", + async ({ request, params }) => { + const url = new URL(request.url); + const { accountId } = params; + const nameParams = url.searchParams.getAll("name"); + + expect(accountId).toEqual("some-account-id"); + expect(nameParams[0]).toEqual(queue); + expect(request.headers.get("authorization")).toEqual( + "Bearer some-api-token" + ); + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: [ + { + queue_id: "queue-id", + queue_name: queue, + created_on: "", + producers: [], + consumers: [], + producers_total_count: 1, + consumers_total_count: 0, + modified_on: "", + }, + ], + }); + }, + { once: true } + ) + ); + await expect( + runWrangler( + `r2 bucket notification create ${bucketName} --queue ${queue} --event-types ${eventTypes.join( + " " + )} --description "rule description"` + ) + ).resolves.toBe(undefined); + expect(std.out).toMatchInlineSnapshot(` + "Creating event notification rule for object creation and deletion (PutObject,CompleteMultipartUpload,CopyObject,DeleteObject,LifecycleDeletion) + Event notification rule created successfully!" + `); + }); + it("errors if required options are not provided", async () => { await expect( runWrangler("r2 bucket notification create notification-test-001") @@ -1028,7 +1280,8 @@ describe("r2", () => { --prefix The prefix that an object must match to emit event notifications (note: regular expressions not supported) [string] --suffix The suffix that an object must match to emit event notifications (note: regular expressions not supported) [string] --queue The name of the queue that will receive event notification messages [string] [required] - -J, --jurisdiction The jurisdiction where the bucket exists [string]" + -J, --jurisdiction The jurisdiction where the bucket exists [string] + --description A description that can be used to identify the event notification rule after creation [string]" `); }); }); diff --git a/packages/wrangler/src/r2/helpers.ts b/packages/wrangler/src/r2/helpers.ts index ee1f33aee9e8..991f60835f87 100644 --- a/packages/wrangler/src/r2/helpers.ts +++ b/packages/wrangler/src/r2/helpers.ts @@ -379,6 +379,7 @@ type NotificationRule = { prefix?: string; suffix?: string; actions: R2EventableOperation[]; + description?: string; }; type GetNotificationRule = { ruleId: string; @@ -550,7 +551,8 @@ export async function putEventNotificationConfig( queueName: string, eventTypes: R2EventType[], prefix?: string, - suffix?: string + suffix?: string, + description?: string ): Promise { const queue = await getQueue(config, queueName); const headers = eventNotificationHeaders(apiCredentials, jurisdiction); @@ -560,9 +562,14 @@ export async function putEventNotificationConfig( actions = actions.concat(actionsForEventCategories[et]); } - const body: PutNotificationRequestBody = { - rules: [{ prefix, suffix, actions }], - }; + const body: PutNotificationRequestBody = + description === undefined + ? { + rules: [{ prefix, suffix, actions }], + } + : { + rules: [{ prefix, suffix, actions, description }], + }; const ruleFor = eventTypes.map((et) => et === "object-create" ? "creation" : "deletion" ); diff --git a/packages/wrangler/src/r2/notification.ts b/packages/wrangler/src/r2/notification.ts index 15d7b7fd6328..e808575d2f32 100644 --- a/packages/wrangler/src/r2/notification.ts +++ b/packages/wrangler/src/r2/notification.ts @@ -94,6 +94,11 @@ export function CreateOptions(yargs: CommonYargsArgv) { alias: "J", requiresArg: true, type: "string", + }) + .option("description", { + describe: + "A description that can be used to identify the event notification rule after creation", + type: "string", }); } @@ -111,6 +116,7 @@ export async function CreateHandler( prefix = "", suffix = "", jurisdiction = "", + description, } = args; await putEventNotificationConfig( config, @@ -121,7 +127,8 @@ export async function CreateHandler( queue, eventTypes as R2EventType[], prefix, - suffix + suffix, + description ); logger.log("Event notification rule created successfully!"); }