Skip to content

Commit

Permalink
Wrangler R2 jurisdictions support (#3775)
Browse files Browse the repository at this point in the history
  • Loading branch information
bthwaites authored Sep 13, 2023
1 parent bdb39ed commit 3af3087
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 50 deletions.
5 changes: 5 additions & 0 deletions .changeset/lazy-beers-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

Jurisdictions support for R2
24 changes: 24 additions & 0 deletions packages/wrangler/src/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5058,6 +5058,16 @@ addEventListener('fetch', event => {});`
r2_buckets: [
{ binding: "R2_BUCKET_ONE", bucket_name: "r2-bucket-one-name" },
{ binding: "R2_BUCKET_TWO", bucket_name: "r2-bucket-two-name" },
{
binding: "R2_BUCKET_ONE_EU",
bucket_name: "r2-bucket-one-name",
jurisdiction: "eu",
},
{
binding: "R2_BUCKET_TWO_EU",
bucket_name: "r2-bucket-two-name",
jurisdiction: "eu",
},
],
analytics_engine_datasets: [
{ binding: "AE_DATASET_ONE", dataset: "ae-dataset-one-name" },
Expand Down Expand Up @@ -5169,6 +5179,18 @@ addEventListener('fetch', event => {});`
name: "R2_BUCKET_TWO",
type: "r2_bucket",
},
{
bucket_name: "r2-bucket-one-name",
jurisdiction: "eu",
name: "R2_BUCKET_ONE_EU",
type: "r2_bucket",
},
{
bucket_name: "r2-bucket-two-name",
jurisdiction: "eu",
name: "R2_BUCKET_TWO_EU",
type: "r2_bucket",
},
{
dataset: "ae-dataset-one-name",
name: "AE_DATASET_ONE",
Expand Down Expand Up @@ -5234,6 +5256,8 @@ addEventListener('fetch', event => {});`
- R2 Buckets:
- R2_BUCKET_ONE: r2-bucket-one-name
- R2_BUCKET_TWO: r2-bucket-two-name
- R2_BUCKET_ONE_EU: r2-bucket-one-name (eu)
- R2_BUCKET_TWO_EU: r2-bucket-two-name (eu)
- logfwdr:
- httplogs: httplogs
- trace: trace
Expand Down
40 changes: 36 additions & 4 deletions packages/wrangler/src/__tests__/r2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ describe("r2", () => {
-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]"
-v, --version Show version number [boolean]
Options:
-J, --jurisdiction The jurisdiction where the new bucket will be created [string]"
`);
expect(std.err).toMatchInlineSnapshot(`
"X [ERROR] Not enough non-option arguments: got 0, need at least 1
Expand Down Expand Up @@ -137,7 +140,10 @@ describe("r2", () => {
-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]"
-v, --version Show version number [boolean]
Options:
-J, --jurisdiction The jurisdiction where the new bucket will be created [string]"
`);
expect(std.err).toMatchInlineSnapshot(`
"X [ERROR] Unknown arguments: def, ghi
Expand All @@ -164,6 +170,26 @@ describe("r2", () => {
Created bucket testBucket."
`);
});

it("should create a bucket with the expected jurisdiction", async () => {
msw.use(
rest.post(
"*/accounts/:accountId/r2/buckets",
async (request, response, context) => {
const { accountId } = request.params;
expect(accountId).toEqual("some-account-id");
expect(request.headers.get("cf-r2-jurisdiction")).toEqual("eu");
expect(await request.json()).toEqual({ name: "testBucket" });
return response.once(context.json(createFetchResult({})));
}
)
);
await runWrangler("r2 bucket create testBucket -J eu");
expect(std.out).toMatchInlineSnapshot(`
"Creating bucket testBucket (eu).
Created bucket testBucket (eu)."
`);
});
});

describe("delete", () => {
Expand All @@ -187,7 +213,10 @@ describe("r2", () => {
-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]"
-v, --version Show version number [boolean]
Options:
-J, --jurisdiction The jurisdiction where the bucket exists [string]"
`);
expect(std.err).toMatchInlineSnapshot(`
"X [ERROR] Not enough non-option arguments: got 0, need at least 1
Expand Down Expand Up @@ -216,7 +245,10 @@ describe("r2", () => {
-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]"
-v, --version Show version number [boolean]
Options:
-J, --jurisdiction The jurisdiction where the bucket exists [string]"
`);
expect(std.err).toMatchInlineSnapshot(`
"X [ERROR] Unknown arguments: def, ghi
Expand Down
2 changes: 2 additions & 0 deletions packages/wrangler/src/config/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,8 @@ interface EnvironmentNonInheritable {
bucket_name: string;
/** The preview name of this R2 bucket at the edge. */
preview_bucket_name?: string;
/** The jurisdiction that the bucket exists in. Default if not present. */
jurisdiction?: string;
}[];

/**
Expand Down
5 changes: 4 additions & 1 deletion packages/wrangler/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,10 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
if (r2_buckets !== undefined && r2_buckets.length > 0) {
output.push({
type: "R2 Buckets",
entries: r2_buckets.map(({ binding, bucket_name }) => {
entries: r2_buckets.map(({ binding, bucket_name, jurisdiction }) => {
if (jurisdiction !== undefined) {
bucket_name += ` (${jurisdiction})`;
}
return {
key: binding,
value: bucket_name,
Expand Down
8 changes: 8 additions & 0 deletions packages/wrangler/src/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2164,6 +2164,14 @@ const validateR2Binding: ValidatorFn = (diagnostics, field, value) => {
);
isValid = false;
}
if (!isOptionalProperty(value, "jurisdiction", "string")) {
diagnostics.errors.push(
`"${field}" bindings should, optionally, have a string "jurisdiction" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}
return isValid;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ export type WorkerMetadataBinding =
environment?: string;
}
| { type: "queue"; name: string; queue_name: string }
| { type: "r2_bucket"; name: string; bucket_name: string }
| {
type: "r2_bucket";
name: string;
bucket_name: string;
jurisdiction?: string;
}
| { type: "d1"; name: string; id: string; internalEnv?: string }
| { type: "constellation"; name: string; project: string }
| { type: "service"; name: string; service: string; environment?: string }
Expand Down Expand Up @@ -163,11 +168,12 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
});
});

bindings.r2_buckets?.forEach(({ binding, bucket_name }) => {
bindings.r2_buckets?.forEach(({ binding, bucket_name, jurisdiction }) => {
metadataBindings.push({
name: binding,
type: "r2_bucket",
bucket_name,
jurisdiction,
});
});

Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/src/deployment-bundle/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export interface CfQueue {
export interface CfR2Bucket {
binding: string;
bucket_name: string;
jurisdiction?: string;
}

// TODO: figure out if this is duplicated in packages/wrangler/src/config/environment.ts
Expand Down
4 changes: 3 additions & 1 deletion packages/wrangler/src/dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ export type AdditionalDevProps = {
binding: string;
bucket_name: string;
preview_bucket_name?: string;
jurisdiction?: string;
}[];
d1Databases?: Environment["d1_databases"];
processEntrypoint?: boolean;
Expand Down Expand Up @@ -889,7 +890,7 @@ function getBindings(
],
r2_buckets: [
...(configParam.r2_buckets?.map(
({ binding, preview_bucket_name, bucket_name }) => {
({ binding, preview_bucket_name, bucket_name, jurisdiction }) => {
// same idea as kv namespace preview id,
// same copy-on-write TODO
if (!preview_bucket_name && !local) {
Expand All @@ -900,6 +901,7 @@ function getBindings(
return {
binding,
bucket_name: preview_bucket_name ?? bucket_name,
jurisdiction,
};
}
) || []),
Expand Down
6 changes: 5 additions & 1 deletion packages/wrangler/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,11 @@ export function mapBindings(bindings: WorkerMetadataBinding[]): RawConfig {
{
configObj.r2_buckets = [
...(configObj.r2_buckets ?? []),
{ binding: binding.name, bucket_name: binding.bucket_name },
{
binding: binding.name,
bucket_name: binding.bucket_name,
jurisdiction: binding.jurisdiction,
},
];
}
break;
Expand Down
53 changes: 43 additions & 10 deletions packages/wrangler/src/r2/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ export interface R2BucketInfo {
* Fetch a list of all the buckets under the given `accountId`.
*/
export async function listR2Buckets(
accountId: string
accountId: string,
jurisdiction?: string
): Promise<R2BucketInfo[]> {
const headers: HeadersInit = {};
if (jurisdiction !== undefined) {
headers["cf-r2-jurisdiction"] = jurisdiction;
}
const results = await fetchResult<{
buckets: R2BucketInfo[];
}>(`/accounts/${accountId}/r2/buckets`);
}>(`/accounts/${accountId}/r2/buckets`, { headers });
return results.buckets;
}

Expand All @@ -41,11 +46,17 @@ export async function listR2Buckets(
*/
export async function createR2Bucket(
accountId: string,
bucketName: string
bucketName: string,
jurisdiction?: string
): Promise<void> {
const headers: HeadersInit = {};
if (jurisdiction !== undefined) {
headers["cf-r2-jurisdiction"] = jurisdiction;
}
return await fetchResult<void>(`/accounts/${accountId}/r2/buckets`, {
method: "POST",
body: JSON.stringify({ name: bucketName }),
headers,
});
}

Expand All @@ -54,11 +65,16 @@ export async function createR2Bucket(
*/
export async function deleteR2Bucket(
accountId: string,
bucketName: string
bucketName: string,
jurisdiction?: string
): Promise<void> {
const headers: HeadersInit = {};
if (jurisdiction !== undefined) {
headers["cf-r2-jurisdiction"] = jurisdiction;
}
return await fetchResult<void>(
`/accounts/${accountId}/r2/buckets/${bucketName}`,
{ method: "DELETE" }
{ method: "DELETE", headers }
);
}

Expand All @@ -82,11 +98,19 @@ export function bucketAndKeyFromObjectPath(objectPath = ""): {
export async function getR2Object(
accountId: string,
bucketName: string,
objectName: string
objectName: string,
jurisdiction?: string
): Promise<ReadableStream> {
const headers: HeadersInit = {};
if (jurisdiction !== undefined) {
headers["cf-r2-jurisdiction"] = jurisdiction;
}
const response = await fetchR2Objects(
`/accounts/${accountId}/r2/buckets/${bucketName}/objects/${objectName}`,
{ method: "GET" }
{
method: "GET",
headers,
}
);

return response.body;
Expand All @@ -100,7 +124,8 @@ export async function putR2Object(
bucketName: string,
objectName: string,
object: Readable | ReadableStream | Buffer,
options: Record<string, unknown>
options: Record<string, unknown>,
jurisdiction?: string
): Promise<void> {
const headerKeys = [
"content-length",
Expand All @@ -116,6 +141,9 @@ export async function putR2Object(
const value = options[key] || "";
if (value && typeof value === "string") headers[key] = value;
}
if (jurisdiction !== undefined) {
headers["cf-r2-jurisdiction"] = jurisdiction;
}

await fetchR2Objects(
`/accounts/${accountId}/r2/buckets/${bucketName}/objects/${objectName}`,
Expand All @@ -133,11 +161,16 @@ export async function putR2Object(
export async function deleteR2Object(
accountId: string,
bucketName: string,
objectName: string
objectName: string,
jurisdiction?: string
): Promise<void> {
const headers: HeadersInit = {};
if (jurisdiction !== undefined) {
headers["cf-r2-jurisdiction"] = jurisdiction;
}
await fetchR2Objects(
`/accounts/${accountId}/r2/buckets/${bucketName}/objects/${objectName}`,
{ method: "DELETE" }
{ method: "DELETE", headers }
);
}

Expand Down
Loading

0 comments on commit 3af3087

Please sign in to comment.