Skip to content

Commit a5f1779

Browse files
authored
Added the ability to list, add, remove, and update R2 bucket custom domains (#7105)
* Added the ability to list, add, remove, and update R2 bucket custom domains. * Add confirmation for removing custom domain from R2 bucket * Remove enabled option from r2 bucket domain add and update. Added confirmation prompt to add. * Update r2 domain tests to check for confirmation prompts
1 parent 6948b70 commit a5f1779

File tree

7 files changed

+619
-31
lines changed

7 files changed

+619
-31
lines changed

.changeset/tame-dryers-end.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Added the ability to list, add, remove, and update R2 bucket custom domains.

packages/wrangler/src/__tests__/r2.test.ts

+199
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { actionsForEventCategories } from "../r2/helpers";
55
import { endEventLoop } from "./helpers/end-event-loop";
66
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
77
import { mockConsoleMethods } from "./helpers/mock-console";
8+
import { mockConfirm } from "./helpers/mock-dialogs";
89
import { useMockIsTTY } from "./helpers/mock-istty";
910
import { createFetchResult, msw, mswR2handlers } from "./helpers/msw";
1011
import { runInTempDir } from "./helpers/run-in-tmp";
@@ -97,6 +98,7 @@ describe("r2", () => {
9798
wrangler r2 bucket delete <name> Delete an R2 bucket
9899
wrangler r2 bucket sippy Manage Sippy incremental migration on an R2 bucket
99100
wrangler r2 bucket notification Manage event notification rules for an R2 bucket
101+
wrangler r2 bucket domain Manage custom domains for an R2 bucket
100102
101103
GLOBAL FLAGS
102104
-j, --experimental-json-config Experimental: support wrangler.json [boolean]
@@ -131,6 +133,7 @@ describe("r2", () => {
131133
wrangler r2 bucket delete <name> Delete an R2 bucket
132134
wrangler r2 bucket sippy Manage Sippy incremental migration on an R2 bucket
133135
wrangler r2 bucket notification Manage event notification rules for an R2 bucket
136+
wrangler r2 bucket domain Manage custom domains for an R2 bucket
134137
135138
GLOBAL FLAGS
136139
-j, --experimental-json-config Experimental: support wrangler.json [boolean]
@@ -1467,6 +1470,202 @@ describe("r2", () => {
14671470
});
14681471
});
14691472
});
1473+
describe("domain", () => {
1474+
const { setIsTTY } = useMockIsTTY();
1475+
mockAccountId();
1476+
mockApiToken();
1477+
describe("add", () => {
1478+
it("should add custom domain to the bucket as expected", async () => {
1479+
const bucketName = "my-bucket";
1480+
const domainName = "example.com";
1481+
const zoneId = "zone-id-123";
1482+
1483+
setIsTTY(true);
1484+
mockConfirm({
1485+
text:
1486+
`Are you sure you want to add the custom domain '${domainName}' to bucket '${bucketName}'? ` +
1487+
`The contents of your bucket will be made publicly available at 'https://${domainName}'`,
1488+
result: true,
1489+
});
1490+
msw.use(
1491+
http.post(
1492+
"*/accounts/:accountId/r2/buckets/:bucketName/domains/custom",
1493+
async ({ request, params }) => {
1494+
const { accountId, bucketName: bucketParam } = params;
1495+
expect(accountId).toEqual("some-account-id");
1496+
expect(bucketName).toEqual(bucketParam);
1497+
const requestBody = await request.json();
1498+
expect(requestBody).toEqual({
1499+
domain: domainName,
1500+
zoneId: zoneId,
1501+
enabled: true,
1502+
minTLS: "1.0",
1503+
});
1504+
return HttpResponse.json(createFetchResult({}));
1505+
},
1506+
{ once: true }
1507+
)
1508+
);
1509+
await runWrangler(
1510+
`r2 bucket domain add ${bucketName} --domain ${domainName} --zone-id ${zoneId}`
1511+
);
1512+
expect(std.out).toMatchInlineSnapshot(`
1513+
"Connecting custom domain 'example.com' to bucket 'my-bucket'...
1514+
✨ Custom domain 'example.com' connected successfully."
1515+
`);
1516+
});
1517+
1518+
it("should error if domain and zone-id are not provided", async () => {
1519+
const bucketName = "my-bucket";
1520+
await expect(
1521+
runWrangler(`r2 bucket domain add ${bucketName}`)
1522+
).rejects.toThrowErrorMatchingInlineSnapshot(
1523+
`[Error: Missing required arguments: domain, zone-id]`
1524+
);
1525+
expect(std.err).toMatchInlineSnapshot(`
1526+
"X [ERROR] Missing required arguments: domain, zone-id
1527+
1528+
"
1529+
`);
1530+
});
1531+
});
1532+
describe("list", () => {
1533+
it("should list custom domains for a bucket as expected", async () => {
1534+
const bucketName = "my-bucket";
1535+
const mockDomains = [
1536+
{
1537+
domain: "example.com",
1538+
enabled: true,
1539+
status: {
1540+
ownership: "verified",
1541+
ssl: "active",
1542+
},
1543+
minTLS: "1.2",
1544+
zoneId: "zone-id-123",
1545+
zoneName: "example-zone",
1546+
},
1547+
{
1548+
domain: "test.com",
1549+
enabled: false,
1550+
status: {
1551+
ownership: "pending",
1552+
ssl: "pending",
1553+
},
1554+
minTLS: "1.0",
1555+
zoneId: "zone-id-456",
1556+
zoneName: "test-zone",
1557+
},
1558+
];
1559+
msw.use(
1560+
http.get(
1561+
"*/accounts/:accountId/r2/buckets/:bucketName/domains/custom",
1562+
async ({ params }) => {
1563+
const { accountId, bucketName: bucketParam } = params;
1564+
expect(accountId).toEqual("some-account-id");
1565+
expect(bucketParam).toEqual(bucketName);
1566+
return HttpResponse.json(
1567+
createFetchResult({
1568+
domains: mockDomains,
1569+
})
1570+
);
1571+
},
1572+
{ once: true }
1573+
)
1574+
);
1575+
await runWrangler(`r2 bucket domain list ${bucketName}`);
1576+
expect(std.out).toMatchInlineSnapshot(`
1577+
"Listing custom domains connected to bucket 'my-bucket'...
1578+
domain: example.com
1579+
enabled: Yes
1580+
ownership_status: verified
1581+
ssl_status: active
1582+
min_tls_version: 1.2
1583+
zone_id: zone-id-123
1584+
zone_name: example-zone
1585+
1586+
domain: test.com
1587+
enabled: No
1588+
ownership_status: pending
1589+
ssl_status: pending
1590+
min_tls_version: 1.0
1591+
zone_id: zone-id-456
1592+
zone_name: test-zone"
1593+
`);
1594+
});
1595+
});
1596+
describe("remove", () => {
1597+
it("should remove a custom domain as expected", async () => {
1598+
const bucketName = "my-bucket";
1599+
const domainName = "example.com";
1600+
setIsTTY(true);
1601+
mockConfirm({
1602+
text:
1603+
`Are you sure you want to remove the custom domain '${domainName}' from bucket '${bucketName}'? ` +
1604+
`Your bucket will no longer be available from 'https://${domainName}'`,
1605+
result: true,
1606+
});
1607+
msw.use(
1608+
http.delete(
1609+
"*/accounts/:accountId/r2/buckets/:bucketName/domains/custom/:domainName",
1610+
async ({ params }) => {
1611+
const {
1612+
accountId,
1613+
bucketName: bucketParam,
1614+
domainName: domainParam,
1615+
} = params;
1616+
expect(accountId).toEqual("some-account-id");
1617+
expect(bucketParam).toEqual(bucketName);
1618+
expect(domainParam).toEqual(domainName);
1619+
return HttpResponse.json(createFetchResult({}));
1620+
},
1621+
{ once: true }
1622+
)
1623+
);
1624+
await runWrangler(
1625+
`r2 bucket domain remove ${bucketName} --domain ${domainName}`
1626+
);
1627+
expect(std.out).toMatchInlineSnapshot(`
1628+
"Removing custom domain 'example.com' from bucket 'my-bucket'...
1629+
Custom domain 'example.com' removed successfully."
1630+
`);
1631+
});
1632+
});
1633+
describe("update", () => {
1634+
it("should update a custom domain as expected", async () => {
1635+
const bucketName = "my-bucket";
1636+
const domainName = "example.com";
1637+
msw.use(
1638+
http.put(
1639+
"*/accounts/:accountId/r2/buckets/:bucketName/domains/custom/:domainName",
1640+
async ({ request, params }) => {
1641+
const {
1642+
accountId,
1643+
bucketName: bucketParam,
1644+
domainName: domainParam,
1645+
} = params;
1646+
expect(accountId).toEqual("some-account-id");
1647+
expect(bucketParam).toEqual(bucketName);
1648+
expect(domainParam).toEqual(domainName);
1649+
const requestBody = await request.json();
1650+
expect(requestBody).toEqual({
1651+
domain: domainName,
1652+
minTLS: "1.3",
1653+
});
1654+
return HttpResponse.json(createFetchResult({}));
1655+
},
1656+
{ once: true }
1657+
)
1658+
);
1659+
await runWrangler(
1660+
`r2 bucket domain update ${bucketName} --domain ${domainName} --min-tls 1.3`
1661+
);
1662+
expect(std.out).toMatchInlineSnapshot(`
1663+
"Updating custom domain 'example.com' for bucket 'my-bucket'...
1664+
✨ Custom domain 'example.com' updated successfully."
1665+
`);
1666+
});
1667+
});
1668+
});
14701669
});
14711670

14721671
describe("r2 object", () => {

packages/wrangler/src/__tests__/r2/helpers.test.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ describe("event notifications", () => {
1313

1414
test("tableFromNotificationsGetResponse", async () => {
1515
const bucketName = "my-bucket";
16-
const config = { account_id: "my-account" };
1716
const response: GetNotificationConfigResponse = {
1817
bucketName,
1918
queues: [
@@ -48,10 +47,7 @@ describe("event notifications", () => {
4847
},
4948
],
5049
};
51-
const tableOutput = await tableFromNotificationGetResponse(
52-
config,
53-
response
54-
);
50+
const tableOutput = tableFromNotificationGetResponse(response);
5551
logger.log(tableOutput.map((x) => formatLabelledValues(x)).join("\n\n"));
5652

5753
await expect(std.out).toMatchInlineSnapshot(`

0 commit comments

Comments
 (0)