npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

radosgw-admin

v0.2.0

Published

Node.js SDK for the Ceph RADOS Gateway Admin Ops API — manage users, buckets, quotas and rate limits programmatically

Readme

radosgw-admin

Node.js SDK for the Ceph RADOS Gateway Admin Ops API — manage users, buckets, quotas, rate limits and access keys programmatically.

CI codecov npm version npm downloads License: Apache 2.0 TypeScript


Why?

The only existing npm package for RGW Admin Ops (rgw-admin-client) was last published 7 years ago — no TypeScript, no ESM, no maintenance. Meanwhile, Ceph adoption in Kubernetes (Rook-Ceph, OpenShift Data Foundation) keeps growing. This package fills that gap.

What you get:

  • Full RGW Admin Ops API coverage — users, keys, subusers, buckets, quotas, rate limits
  • Zero runtime dependencies — AWS SigV4 signing uses only node:crypto
  • Request hooks — add logging, Prometheus metrics, or audit trails via onBeforeRequest/onAfterResponse
  • Health check — rgw.healthCheck() for one-liner connectivity verification
  • Structured error hierarchy — catch specific failures, not generic HTTP errors
  • Automatic snake_case/camelCase conversion — idiomatic JS API over RGW's REST interface
  • TypeScript with strict types and zero any — every request and response is fully typed
  • Dual ESM + CJS build — works in every Node.js environment

Install

npm install radosgw-admin
# or
yarn add radosgw-admin
# or
pnpm add radosgw-admin
# or
bun add radosgw-admin

Requires Node.js >= 18 and a Ceph RGW instance with the Admin Ops API enabled.

Quick Start

import { RadosGWAdminClient } from 'radosgw-admin';

const rgw = new RadosGWAdminClient({
  host: 'http://ceph-rgw.example.com',
  port: 8080,
  accessKey: 'ADMIN_ACCESS_KEY',
  secretKey: 'ADMIN_SECRET_KEY',
});

// Create a user
const user = await rgw.users.create({
  uid: 'alice',
  displayName: 'Alice',
  email: '[email protected]',
  maxBuckets: 100,
});

// List all users
const uids = await rgw.users.list();

// Get user info with keys, caps, quotas
const info = await rgw.users.get('alice');

// Suspend / re-enable
await rgw.users.suspend('alice');
await rgw.users.enable('alice');

// Delete (with optional purge of all objects)
await rgw.users.delete({ uid: 'alice', purgeData: true });

Configuration

const rgw = new RadosGWAdminClient({
  host: 'https://ceph-rgw.example.com', // Required — RGW endpoint
  port: 443, // Optional — omit to use host URL default
  accessKey: 'ADMIN_KEY', // Required — admin user access key
  secretKey: 'ADMIN_SECRET', // Required — admin user secret key
  adminPath: '/admin', // Optional — API prefix (default: "/admin")
  timeout: 15000, // Optional — request timeout in ms (default: 10000)
  region: 'us-east-1', // Optional — SigV4 region (default: "us-east-1")
  insecure: false, // Optional — skip TLS verification (default: false)
  debug: false, // Optional — enable request/response logging (default: false)
  maxRetries: 3, // Optional — retry transient errors (default: 0)
  retryDelay: 200, // Optional — base delay for exponential backoff w/ jitter in ms (default: 200)
  userAgent: 'my-app/1.0', // Optional — custom User-Agent (default: "radosgw-admin/<ver> node/<ver>")
  onBeforeRequest: [(ctx) => {}], // Optional — hooks called before each request
  onAfterResponse: [(ctx) => {}], // Optional — hooks called after each response
});

API Reference

Users

rgw.users.create(input);             // Create a new RGW user
rgw.users.get(uid, tenant?);         // Get full user info (keys, caps, quotas)
rgw.users.getByAccessKey(accessKey); // Look up a user by their S3 access key
rgw.users.modify(input);             // Update display name, email, max buckets, etc.
rgw.users.delete(input);             // Delete user (optionally purge all data)
rgw.users.list();                    // List all user IDs in the cluster
rgw.users.suspend(uid);              // Suspend a user account
rgw.users.enable(uid);               // Re-enable a suspended user
rgw.users.getStats(uid, sync?);      // Get storage usage statistics

Keys

rgw.keys.generate(input); // Generate or assign S3/Swift keys
rgw.keys.revoke(input);   // Revoke (delete) a key pair

Subusers.

rgw.subusers.create(input); // Create a subuser for an existing user
rgw.subusers.modify(input); // Modify subuser permissions
rgw.subusers.remove(input); // Remove a subuser
// Create a Swift subuser with full access
await rgw.subusers.create({
  uid: 'alice',
  subuser: 'alice:swift',
  access: 'full',
  keyType: 'swift',
  generateSecret: true,
});

// Restrict to read-only
await rgw.subusers.modify({
  uid: 'alice',
  subuser: 'alice:swift',
  access: 'read',
});

// Remove the subuser
await rgw.subusers.remove({ uid: 'alice', subuser: 'alice:swift' });

Buckets

rgw.buckets.list();                // List all buckets in the cluster
rgw.buckets.listByUser(uid);      // List buckets owned by a specific user
rgw.buckets.getInfo(bucket);       // Get bucket metadata and stats
rgw.buckets.delete(input);         // Delete a bucket (optionally purge objects)
rgw.buckets.transferOwnership(input); // Transfer bucket to a different user
rgw.buckets.removeOwnership(input);   // Remove bucket ownership
rgw.buckets.verifyIndex(input);    // Check and optionally repair bucket index
// List all buckets in the cluster
const allBuckets = await rgw.buckets.list();

// List buckets owned by a specific user
const userBuckets = await rgw.buckets.listByUser('alice');

// Get detailed bucket info
const info = await rgw.buckets.getInfo('my-bucket');
console.log(info.usage.rgwMain.numObjects);

// Transfer a bucket to a different user
await rgw.buckets.transferOwnership({
  bucket: 'my-bucket',
  bucketId: info.id,
  uid: 'bob',
});

// Check and repair bucket index
const result = await rgw.buckets.verifyIndex({
  bucket: 'my-bucket',
  checkObjects: true,
  fix: true,
});

// Delete bucket and all its objects
await rgw.buckets.delete({ bucket: 'my-bucket', purgeObjects: true });

Quotas

rgw.quota.getUserQuota(uid);        // Get user-level quota
rgw.quota.setUserQuota(input);      // Set user-level quota (accepts "10G" size strings)
rgw.quota.enableUserQuota(uid);     // Enable user quota without changing values
rgw.quota.disableUserQuota(uid);    // Disable user quota without changing values
rgw.quota.getBucketQuota(uid);      // Get bucket-level quota for a user
rgw.quota.setBucketQuota(input);    // Set bucket-level quota
rgw.quota.enableBucketQuota(uid);   // Enable bucket quota
rgw.quota.disableBucketQuota(uid);  // Disable bucket quota

maxSize accepts a number (bytes) or a human-readable string with binary (1024-based) units:

| Input | Bytes | |-------|-------| | '1K' / '1KB' | 1,024 | | '500M' / '500MB' | 524,288,000 | | '10G' / '10GB' | 10,737,418,240 | | '1T' / '1TB' | 1,099,511,627,776 | | '1.5G' | 1,610,612,736 | | 1073741824 | 1,073,741,824 (raw bytes) | | -1 | Unlimited |

// Set a 10GB user quota with 50k object limit
await rgw.quota.setUserQuota({
  uid: 'alice',
  maxSize: '10G',       // → 10737418240 bytes
  maxObjects: 50000,
  enabled: true,        // default: true when setting
});

// Size limit only, unlimited objects
await rgw.quota.setUserQuota({
  uid: 'alice',
  maxSize: '10G',
  maxObjects: -1,       // -1 = unlimited
});

// Check current quota — maxSize is always returned as bytes
const quota = await rgw.quota.getUserQuota('alice');
console.log('Enabled:', quota.enabled);
console.log('Max size:', quota.maxSize, 'bytes');

// Disable quota temporarily (preserves values)
await rgw.quota.disableUserQuota('alice');

// Set a 1GB per-bucket quota (applies to all buckets owned by the user)
await rgw.quota.setBucketQuota({
  uid: 'alice',         // uid, not bucket name — RGW bucket quotas are per-user
  maxSize: '1G',
  maxObjects: 10000,
});

Rate Limits

Requires Ceph Pacific (v16) or later. Values are per RGW instance — divide by the number of RGW daemons for cluster-wide limits.

rgw.rateLimit.getUserLimit(uid);           // Get user rate limit
rgw.rateLimit.setUserLimit(input);         // Set user rate limit
rgw.rateLimit.disableUserLimit(uid);       // Disable user rate limit
rgw.rateLimit.getBucketLimit(bucket);      // Get bucket rate limit
rgw.rateLimit.setBucketLimit(input);       // Set bucket rate limit
rgw.rateLimit.disableBucketLimit(bucket);  // Disable bucket rate limit
rgw.rateLimit.getGlobal();                 // Get global rate limits (user/bucket/anonymous)
rgw.rateLimit.setGlobal(input);            // Set global rate limit for a scope
// Throttle alice to 100 read ops/min and 50MB/min write per RGW instance
await rgw.rateLimit.setUserLimit({
  uid: 'alice',
  maxReadOps: 100,
  maxWriteOps: 50,
  maxWriteBytes: 52428800,  // 50MB
});

// Disable rate limit temporarily (preserves config)
await rgw.rateLimit.disableUserLimit('alice');

// Set a bucket-level rate limit
await rgw.rateLimit.setBucketLimit({
  bucket: 'public-assets',
  maxReadOps: 200,
  maxWriteOps: 10,
});

// Protect public-read buckets from anonymous abuse
await rgw.rateLimit.setGlobal({
  scope: 'anonymous',
  maxReadOps: 50,
  maxWriteOps: 0,
  enabled: true,
});

// View all global rate limits
const global = await rgw.rateLimit.getGlobal();
console.log('Anonymous:', global.anonymous);
console.log('User:', global.user);
console.log('Bucket:', global.bucket);

Usage & Analytics

Prerequisite: Usage logging must be enabled in ceph.conf: rgw enable usage log = true. Restart RGW daemons after changing the config.

rgw.usage.get(input?);   // Query usage report (per-user or cluster-wide)
rgw.usage.trim(input?);  // Delete old usage logs — requires removeAll: true when no uid
// Usage for alice in January 2025
const report = await rgw.usage.get({
  uid: 'alice',
  start: '2025-01-01',   // accepts 'YYYY-MM-DD' or Date object
  end: '2025-01-31',
});

for (const summary of report.summary) {
  for (const cat of summary.categories) {
    console.log(`[${cat.category}] ops: ${cat.ops} | sent: ${(cat.bytesSent / 1e6).toFixed(2)} MB`);
  }
  console.log('Total sent:', (summary.total.bytesSent / 1e9).toFixed(3), 'GB');
}

// Cluster-wide usage, all time (omit all filters)
const all = await rgw.usage.get();

// Trim a single user's logs up to end of 2024
await rgw.usage.trim({ uid: 'alice', end: '2024-12-31' });

// ⚠️  Trim all cluster usage logs — removeAll: true required when no uid
await rgw.usage.trim({ end: '2023-12-31', removeAll: true });

Cluster Info

rgw.info.get();   // Get cluster FSID and basic endpoint info
const info = await rgw.info.get();
console.log('Cluster FSID:', info.info.storageBackends[0].clusterId);

Error Handling

Every error thrown is an instance of RGWError with structured properties:

import { RGWNotFoundError, RGWConflictError, RGWAuthError } from 'radosgw-admin';

try {
  await rgw.users.get('nonexistent');
} catch (error) {
  if (error instanceof RGWNotFoundError) {
    // 404 — user does not exist
  } else if (error instanceof RGWAuthError) {
    // 403 — check admin credentials / caps
  }
}

| Error Class | HTTP Status | Retryable | Condition | | -------------------- | --------------- | --------- | ---------------------------------------- | | RGWValidationError | 400 / (pre-request) | No | Invalid input (missing uid, bad params) | | RGWNotFoundError | 404 | No | Resource does not exist | | RGWConflictError | 409 | No | Resource already exists | | RGWAuthError | 403 | No | Insufficient credentials or capabilities | | RGWRateLimitError | 429 | Yes | Rate limit exceeded | | RGWServiceError | 5xx | Yes | RGW server error |

All errors include a code field with the RGW error code (e.g., NoSuchUser, BucketAlreadyExists, SlowDown).

Note: Destructive operations (purgeData, purgeObjects) emit a console.warn before executing. To suppress in CI/automation, redirect stderr or patch console.warn.

Health Check

Verify RGW connectivity before running operations:

const ok = await rgw.healthCheck();
if (!ok) throw new Error('Cannot reach RGW');

Request Hooks

Add logging, metrics, or telemetry without modifying the SDK:

const rgw = new RadosGWAdminClient({
  host: 'http://rgw:8080',
  accessKey: '...',
  secretKey: '...',
  onBeforeRequest: [
    (ctx) => console.log(`→ ${ctx.method} ${ctx.path}`),
  ],
  onAfterResponse: [
    (ctx) => console.log(`← ${ctx.status} in ${ctx.durationMs}ms`),
  ],
});

Hooks run on every request across all modules. If a hook throws, the error is swallowed — hooks never break RGW operations.

Hook context includes: method, path, url, query, attempt (retry number), startTime, and for after-hooks: status, durationMs, error.

Security note: Hook context includes the full request URL which may contain sensitive query parameters (e.g. secret-key when creating users with pre-specified credentials). If you log or send hook data to external systems, redact sensitive fields. The SDK already redacts secret-key from its own debug logs, but hooks receive the raw URL.

Request Cancellation

Pass an AbortSignal to cancel in-flight requests:

const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

await rgw._client.request({
  method: 'GET',
  path: '/user',
  signal: controller.signal,
});

Compatibility

Tested against Ceph Quincy (v17) and Reef (v18). The Admin Ops API is available in all Ceph releases with RGW.

Prerequisites:

  • The RGW admin user must have users=*, buckets=* capabilities
  • Admin Ops API must be accessible (default path: /admin)
  • For insecure: true — only use with self-signed certificates in dev/test environments

FAQ

Set insecure: true in the client config. This skips TLS certificate verification — use only in dev/test environments:

const rgw = new RadosGWAdminClient({
  host: 'https://rgw.internal',
  accessKey: '...',
  secretKey: '...',
  insecure: true, // skips TLS verification
});

The Admin Ops API has been available since Ceph Luminous (v12). This package is tested against Quincy (v17) and Reef (v18).

| Feature | Minimum Ceph Version | |---|---| | Users, keys, subusers, buckets | Luminous (v12) | | Quotas | Luminous (v12) | | Rate limits | Pacific (v16) | | Usage logging | Luminous (v12) |

Enable debug mode to see full HTTP request/response details:

const rgw = new RadosGWAdminClient({
  host: 'http://rgw.example.com',
  accessKey: '...',
  secretKey: '...',
  debug: true, // logs request method, URL, headers, and response
});

Common issues:

  • 403 AccessDenied — admin user lacks required capabilities. Grant with: radosgw-admin caps add --uid=admin --caps="users=*;buckets=*"
  • Connection refused — check host/port and that the RGW daemon is running
  • Timeout — increase the timeout value (default: 10000ms) or check network connectivity

Yes. Port-forward or expose the RGW service, then point the client at it:

kubectl port-forward svc/rook-ceph-rgw-my-store 8080:80 -n rook-ceph
const rgw = new RadosGWAdminClient({
  host: 'http://localhost',
  port: 8080,
  accessKey: '...',
  secretKey: '...',
});

To get admin credentials from Rook:

kubectl get secret rook-ceph-dashboard-admin-gateway -n rook-ceph -o jsonpath='{.data.accessKey}' | base64 -d
kubectl get secret rook-ceph-dashboard-admin-gateway -n rook-ceph -o jsonpath='{.data.secretKey}' | base64 -d

Yes. ODF uses Ceph under the hood. Point the client at the RGW route or service endpoint. The admin credentials are stored in the ocs-storagecluster-ceph-rgw-admin-ops-user secret in the openshift-storage namespace.

AWS SigV4 signing is implemented using only node:crypto (built-in). No aws-sdk, no axios, no node-fetch. This means:

  • Smaller node_modules footprint
  • No supply chain risk from transitive dependencies
  • No version conflicts with other packages in your project

Development

git clone https://github.com/nycanshu/radosgw-admin.git
cd radosgw-admin
npm install

npm run build        # ESM + CJS via tsup
npm run typecheck    # tsc --noEmit (strict)
npm test             # vitest
npm run lint         # eslint
npm run format       # prettier
npm run check        # all of the above

See CONTRIBUTING.md for guidelines.

License

Apache 2.0 © nycanshu