Skip to content

nycanshu/radosgw-admin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

142 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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
  • No third-party dependencies — SigV4 signing uses only node:crypto; TLS scoping uses undici (Node.js's own built-in HTTP client, zero transitive deps)
  • 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: 'alice@example.com',
  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
Subuser examples
// 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
Bucket examples
// 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
Quota examples

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
Rate limit examples
// 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 examples
// 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

How do I connect to an RGW with a self-signed certificate?

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
});
Which Ceph versions are supported?

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)
How do I troubleshoot connection issues?

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
Can I use this with Rook-Ceph on Kubernetes?

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
Does this package work with OpenShift Data Foundation (ODF)?

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.

Why no third-party dependencies?

SigV4 signing is implemented using only node:crypto (built-in). TLS scoping (for insecure: true) uses undici — the HTTP client library that Node.js itself uses internally for fetch. undici has zero transitive dependencies and is maintained by the Node.js team. No aws-sdk, no axios, no node-fetch. This means:

  • Minimal 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

About

Node.js SDK for the Ceph RADOS Gateway Admin Ops API — manage users, buckets, quotas and rate limits programmatically. Zero dependencies, TypeScript, dual ESM/CJS.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors