Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/tame-dryers-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

Added the ability to list, add, remove, and update R2 bucket custom domains.
199 changes: 199 additions & 0 deletions packages/wrangler/src/__tests__/r2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { actionsForEventCategories } from "../r2/helpers";
import { endEventLoop } from "./helpers/end-event-loop";
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
import { mockConsoleMethods } from "./helpers/mock-console";
import { mockConfirm } from "./helpers/mock-dialogs";
import { useMockIsTTY } from "./helpers/mock-istty";
import { createFetchResult, msw, mswR2handlers } from "./helpers/msw";
import { runInTempDir } from "./helpers/run-in-tmp";
Expand Down Expand Up @@ -97,6 +98,7 @@ describe("r2", () => {
wrangler r2 bucket delete <name> Delete an R2 bucket
wrangler r2 bucket sippy Manage Sippy incremental migration on an R2 bucket
wrangler r2 bucket notification Manage event notification rules for an R2 bucket
wrangler r2 bucket domain Manage custom domains for an R2 bucket

GLOBAL FLAGS
-j, --experimental-json-config Experimental: support wrangler.json [boolean]
Expand Down Expand Up @@ -131,6 +133,7 @@ describe("r2", () => {
wrangler r2 bucket delete <name> Delete an R2 bucket
wrangler r2 bucket sippy Manage Sippy incremental migration on an R2 bucket
wrangler r2 bucket notification Manage event notification rules for an R2 bucket
wrangler r2 bucket domain Manage custom domains for an R2 bucket

GLOBAL FLAGS
-j, --experimental-json-config Experimental: support wrangler.json [boolean]
Expand Down Expand Up @@ -1467,6 +1470,202 @@ describe("r2", () => {
});
});
});
describe("domain", () => {
const { setIsTTY } = useMockIsTTY();
mockAccountId();
mockApiToken();
describe("add", () => {
it("should add custom domain to the bucket as expected", async () => {
const bucketName = "my-bucket";
const domainName = "example.com";
const zoneId = "zone-id-123";

setIsTTY(true);
mockConfirm({
text:
`Are you sure you want to add the custom domain '${domainName}' to bucket '${bucketName}'? ` +
`The contents of your bucket will be made publicly available at 'https://${domainName}'`,
result: true,
});
msw.use(
http.post(
"*/accounts/:accountId/r2/buckets/:bucketName/domains/custom",
async ({ request, params }) => {
const { accountId, bucketName: bucketParam } = params;
expect(accountId).toEqual("some-account-id");
expect(bucketName).toEqual(bucketParam);
const requestBody = await request.json();
expect(requestBody).toEqual({
domain: domainName,
zoneId: zoneId,
enabled: true,
minTLS: "1.0",
});
return HttpResponse.json(createFetchResult({}));
},
{ once: true }
)
);
await runWrangler(
`r2 bucket domain add ${bucketName} --domain ${domainName} --zone-id ${zoneId}`
);
expect(std.out).toMatchInlineSnapshot(`
"Connecting custom domain 'example.com' to bucket 'my-bucket'...
✨ Custom domain 'example.com' connected successfully."
`);
});

it("should error if domain and zone-id are not provided", async () => {
const bucketName = "my-bucket";
await expect(
runWrangler(`r2 bucket domain add ${bucketName}`)
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Missing required arguments: domain, zone-id]`
);
expect(std.err).toMatchInlineSnapshot(`
"X [ERROR] Missing required arguments: domain, zone-id

"
`);
});
});
describe("list", () => {
it("should list custom domains for a bucket as expected", async () => {
const bucketName = "my-bucket";
const mockDomains = [
{
domain: "example.com",
enabled: true,
status: {
ownership: "verified",
ssl: "active",
},
minTLS: "1.2",
zoneId: "zone-id-123",
zoneName: "example-zone",
},
{
domain: "test.com",
enabled: false,
status: {
ownership: "pending",
ssl: "pending",
},
minTLS: "1.0",
zoneId: "zone-id-456",
zoneName: "test-zone",
},
];
msw.use(
http.get(
"*/accounts/:accountId/r2/buckets/:bucketName/domains/custom",
async ({ params }) => {
const { accountId, bucketName: bucketParam } = params;
expect(accountId).toEqual("some-account-id");
expect(bucketParam).toEqual(bucketName);
return HttpResponse.json(
createFetchResult({
domains: mockDomains,
})
);
},
{ once: true }
)
);
await runWrangler(`r2 bucket domain list ${bucketName}`);
expect(std.out).toMatchInlineSnapshot(`
"Listing custom domains connected to bucket 'my-bucket'...
domain: example.com
enabled: Yes
ownership_status: verified
ssl_status: active
min_tls_version: 1.2
zone_id: zone-id-123
zone_name: example-zone

domain: test.com
enabled: No
ownership_status: pending
ssl_status: pending
min_tls_version: 1.0
zone_id: zone-id-456
zone_name: test-zone"
`);
});
});
describe("remove", () => {
it("should remove a custom domain as expected", async () => {
const bucketName = "my-bucket";
const domainName = "example.com";
setIsTTY(true);
mockConfirm({
text:
`Are you sure you want to remove the custom domain '${domainName}' from bucket '${bucketName}'? ` +
`Your bucket will no longer be available from 'https://${domainName}'`,
result: true,
});
msw.use(
http.delete(
"*/accounts/:accountId/r2/buckets/:bucketName/domains/custom/:domainName",
async ({ params }) => {
const {
accountId,
bucketName: bucketParam,
domainName: domainParam,
} = params;
expect(accountId).toEqual("some-account-id");
expect(bucketParam).toEqual(bucketName);
expect(domainParam).toEqual(domainName);
return HttpResponse.json(createFetchResult({}));
},
{ once: true }
)
);
await runWrangler(
`r2 bucket domain remove ${bucketName} --domain ${domainName}`
);
expect(std.out).toMatchInlineSnapshot(`
"Removing custom domain 'example.com' from bucket 'my-bucket'...
Custom domain 'example.com' removed successfully."
`);
});
});
describe("update", () => {
it("should update a custom domain as expected", async () => {
const bucketName = "my-bucket";
const domainName = "example.com";
msw.use(
http.put(
"*/accounts/:accountId/r2/buckets/:bucketName/domains/custom/:domainName",
async ({ request, params }) => {
const {
accountId,
bucketName: bucketParam,
domainName: domainParam,
} = params;
expect(accountId).toEqual("some-account-id");
expect(bucketParam).toEqual(bucketName);
expect(domainParam).toEqual(domainName);
const requestBody = await request.json();
expect(requestBody).toEqual({
domain: domainName,
minTLS: "1.3",
});
return HttpResponse.json(createFetchResult({}));
},
{ once: true }
)
);
await runWrangler(
`r2 bucket domain update ${bucketName} --domain ${domainName} --min-tls 1.3`
);
expect(std.out).toMatchInlineSnapshot(`
"Updating custom domain 'example.com' for bucket 'my-bucket'...
✨ Custom domain 'example.com' updated successfully."
`);
});
});
});
});

describe("r2 object", () => {
Expand Down
6 changes: 1 addition & 5 deletions packages/wrangler/src/__tests__/r2/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ describe("event notifications", () => {

test("tableFromNotificationsGetResponse", async () => {
const bucketName = "my-bucket";
const config = { account_id: "my-account" };
const response: GetNotificationConfigResponse = {
bucketName,
queues: [
Expand Down Expand Up @@ -48,10 +47,7 @@ describe("event notifications", () => {
},
],
};
const tableOutput = await tableFromNotificationGetResponse(
config,
response
);
const tableOutput = tableFromNotificationGetResponse(response);
logger.log(tableOutput.map((x) => formatLabelledValues(x)).join("\n\n"));

await expect(std.out).toMatchInlineSnapshot(`
Expand Down
Loading
Loading