Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added wrangler r2 bucket info command, improved formatting of list command #7212

Merged
merged 1 commit into from
Nov 13, 2024
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-bobcats-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

Added r2 bucket info command to Wrangler. Improved formatting of r2 bucket list output
121 changes: 83 additions & 38 deletions packages/wrangler/src/__tests__/r2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { runInTempDir } from "./helpers/run-in-tmp";
import { runWrangler } from "./helpers/run-wrangler";
import type {
PutNotificationRequestBody,
R2BucketInfo,
R2EventableOperation,
R2EventType,
} from "../r2/helpers";
Expand Down Expand Up @@ -92,14 +91,15 @@ describe("r2", () => {
Manage R2 buckets

COMMANDS
wrangler r2 bucket create <name> Create a new R2 bucket
wrangler r2 bucket update Update bucket state
wrangler r2 bucket list List R2 buckets
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
wrangler r2 bucket dev-url Manage public access via the r2.dev URL for an R2 bucket
wrangler r2 bucket create <name> Create a new R2 bucket
wrangler r2 bucket update Update bucket state
wrangler r2 bucket list List R2 buckets
wrangler r2 bucket info <bucket> Get information about an R2 bucket
wrangler r2 bucket delete <bucket> 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
wrangler r2 bucket dev-url Manage public access via the r2.dev URL for an R2 bucket

GLOBAL FLAGS
-j, --experimental-json-config Experimental: support wrangler.json [boolean]
Expand Down Expand Up @@ -128,14 +128,15 @@ describe("r2", () => {
Manage R2 buckets

COMMANDS
wrangler r2 bucket create <name> Create a new R2 bucket
wrangler r2 bucket update Update bucket state
wrangler r2 bucket list List R2 buckets
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
wrangler r2 bucket dev-url Manage public access via the r2.dev URL for an R2 bucket
wrangler r2 bucket create <name> Create a new R2 bucket
wrangler r2 bucket update Update bucket state
wrangler r2 bucket list List R2 buckets
wrangler r2 bucket info <bucket> Get information about an R2 bucket
wrangler r2 bucket delete <bucket> 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
wrangler r2 bucket dev-url Manage public access via the r2.dev URL for an R2 bucket

GLOBAL FLAGS
-j, --experimental-json-config Experimental: support wrangler.json [boolean]
Expand All @@ -148,9 +149,15 @@ describe("r2", () => {

describe("list", () => {
it("should list buckets & check request inputs", async () => {
const expectedBuckets: R2BucketInfo[] = [
{ name: "bucket-1-local-once", creation_date: "01-01-2001" },
{ name: "bucket-2-local-once", creation_date: "01-01-2001" },
const mockBuckets = [
{
name: "bucket-1-local-once",
creation_date: "01-01-2001",
},
{
name: "bucket-2-local-once",
creation_date: "01-01-2001",
},
];
msw.use(
http.get(
Expand All @@ -161,27 +168,65 @@ describe("r2", () => {
expect(await request.text()).toEqual("");
return HttpResponse.json(
createFetchResult({
buckets: [
{
name: "bucket-1-local-once",
creation_date: "01-01-2001",
},
{
name: "bucket-2-local-once",
creation_date: "01-01-2001",
},
],
buckets: mockBuckets,
})
);
},
{ once: true }
)
);
await runWrangler("r2 bucket list");

expect(std.err).toMatchInlineSnapshot(`""`);
const buckets = JSON.parse(std.out);
expect(buckets).toEqual(expectedBuckets);
await runWrangler(`r2 bucket list`);
expect(std.out).toMatchInlineSnapshot(`
"Listing buckets...
name: bucket-1-local-once
creation_date: 01-01-2001

name: bucket-2-local-once
creation_date: 01-01-2001"
`);
});
});

describe("info", () => {
it("should get information for the given bucket", async () => {
const bucketName = "my-bucket";
const bucketInfo = {
name: bucketName,
creation_date: "01-01-2001",
location: "WNAM",
storage_class: "Standard",
};

msw.use(
http.get(
"*/accounts/:accountId/r2/buckets/:bucketName",
async ({ params }) => {
const { accountId, bucketName: bucketParam } = params;
expect(accountId).toEqual("some-account-id");
expect(bucketParam).toEqual(bucketName);
return HttpResponse.json(
createFetchResult({
...bucketInfo,
})
);
},
{ once: true }
),
http.post("*/graphql", async () => {
return HttpResponse.json(createFetchResult({}));
})
);
await runWrangler(`r2 bucket info ${bucketName}`);
expect(std.out).toMatchInlineSnapshot(`
"Getting info for 'my-bucket'...
name: my-bucket
created: 01-01-2001
location: WNAM
default_storage_class: Standard
object_count: 0
bucket_size: 0 B"
`);
});
});

Expand Down Expand Up @@ -475,12 +520,12 @@ binding = \\"testBucket\\""
);
expect(std.out).toMatchInlineSnapshot(`
"
wrangler r2 bucket delete <name>
wrangler r2 bucket delete <bucket>

Delete an R2 bucket

POSITIONALS
name The name of the bucket to delete [string] [required]
bucket The name of the bucket to delete [string] [required]

GLOBAL FLAGS
-j, --experimental-json-config Experimental: support wrangler.json [boolean]
Expand Down Expand Up @@ -515,12 +560,12 @@ binding = \\"testBucket\\""
);
expect(std.out).toMatchInlineSnapshot(`
"
wrangler r2 bucket delete <name>
wrangler r2 bucket delete <bucket>

Delete an R2 bucket

POSITIONALS
name The name of the bucket to delete [string] [required]
bucket The name of the bucket to delete [string] [required]

GLOBAL FLAGS
-j, --experimental-json-config Experimental: support wrangler.json [boolean]
Expand Down
134 changes: 133 additions & 1 deletion packages/wrangler/src/r2/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Miniflare } from "miniflare";
import { fetchResult } from "../cfetch";
import prettyBytes from "pretty-bytes";
import { fetchGraphqlResult, fetchResult } from "../cfetch";
import { fetchR2Objects } from "../cfetch/internal";
import { getLocalPersistencePath } from "../dev/get-local-persistence-path";
import { buildPersistOptions } from "../dev/miniflare";
Expand All @@ -20,6 +21,29 @@ import type { HeadersInit } from "undici";
export interface R2BucketInfo {
name: string;
creation_date: string;
location?: string;
storage_class?: string;
}

export interface R2BucketMetrics {
max?: {
objectCount?: number;
payloadSize?: number;
metadataSize?: number;
};
dimensions: {
datetime?: string;
};
}

export interface R2BucketMetricsGraphQLResponse {
data: {
viewer: {
accounts: {
r2StorageAdaptiveGroups?: R2BucketMetrics[];
}[];
};
};
}

/**
Expand All @@ -39,6 +63,114 @@ export async function listR2Buckets(
return results.buckets;
}

export function tablefromR2BucketsListResponse(buckets: R2BucketInfo[]): {
name: string;
creation_date: string;
}[] {
const rows = [];
for (const bucket of buckets) {
rows.push({
name: bucket.name,
creation_date: bucket.creation_date,
});
}
return rows;
}

export async function getR2Bucket(
accountId: string,
bucketName: string,
jurisdiction?: string
): Promise<R2BucketInfo> {
const headers: HeadersInit = {};
if (jurisdiction !== undefined) {
headers["cf-r2-jurisdiction"] = jurisdiction;
}
const result = await fetchResult<R2BucketInfo>(
`/accounts/${accountId}/r2/buckets/${bucketName}`,
{
method: "GET",
headers,
}
);
return result;
}

export async function getR2BucketMetrics(
accountId: string,
bucketName: string,
jurisdiction?: string
): Promise<{ objectCount: number; totalSize: string }> {
const today = new Date();
const yesterday = new Date(new Date(today).setDate(today.getDate() - 1));

let fullBucketName = bucketName;
if (jurisdiction) {
fullBucketName = `${jurisdiction}_${bucketName}`;
}

const storageMetricsQuery = `
query getR2StorageMetrics($accountTag: String, $filter: R2StorageAdaptiveGroupsFilter_InputObject) {
viewer {
accounts(filter: { accountTag: $accountTag }) {
r2StorageAdaptiveGroups(
limit: 1
filter: $filter
orderBy: [datetime_DESC]
) {
max {
objectCount
payloadSize
metadataSize
}
dimensions {
datetime
}
}
}
}
}
`;

const variables = {
accountTag: accountId,
filter: {
datetime_geq: yesterday.toISOString(),
datetime_leq: today.toISOString(),
bucketName: fullBucketName,
},
};
const storageMetricsResult =
await fetchGraphqlResult<R2BucketMetricsGraphQLResponse>({
method: "POST",
body: JSON.stringify({
query: storageMetricsQuery,
operationName: "getR2StorageMetrics",
variables,
}),
headers: {
"Content-Type": "application/json",
},
});

if (storageMetricsResult) {
const metricsData =
storageMetricsResult.data?.viewer?.accounts[0]
?.r2StorageAdaptiveGroups?.[0];
if (metricsData && metricsData.max) {
const objectCount = metricsData.max.objectCount || 0;
const totalSize =
(metricsData.max.payloadSize || 0) +
(metricsData.max.metadataSize || 0);
return {
objectCount,
totalSize: prettyBytes(totalSize),
};
}
}
return { objectCount: 0, totalSize: "0 B" };
}

/**
* Create a bucket with the given `bucketName` within the account given by `accountId`.
*
Expand Down
Loading
Loading