Files
suaveplan/openspec/archive/2026-05-16-add-storage-cdn-rackspace/proposal.md
2026-05-27 23:29:53 +01:00

6.6 KiB

Add @suaveplan/storage-cdn-rackspace — Rackspace Cloud Files + Rackspace CDN adapter

Package: @suaveplan/storage-cdn-rackspace Status: PROPOSED Type: new-package (genesis-original)

  • Design: ./design.md
  • Spec delta: ./specs/storage-cdn-rackspace/spec.md

Why

The user operates production traffic from Rackspace CDN (LON region) with Cloud Files as origin. storage-http covers generic prefix-based reads but knows nothing about Cloud Files auth, container CDN-enablement, purge, or TempURL signing. This package provides a first-class CdnAdapter so application code can write storage.set(key, bytes) / storage.get(key) / storage.purge(key) / storage.signUrl(key, { ttlSeconds }) against Rackspace without leaking provider knowledge into business logic.

Rackspace Cloud Files is OpenStack Swift; the API is well-documented and stable. The CDN side adds container CDN enablement, X-Cdn-Uri discovery, and a per-account TempURL HMAC key. This package implements both layers via injected FetchLike so it can be tested without live API access.

What changes

  • ADDED: RackspaceCdnAdapter class extending CdnAdapter from @suaveplan/storage-cdn.
  • ADDED: createRackspaceCdnAdapter(options) factory.
  • ADDED: RackspaceCdnAdapterOptions interface + Zod schema. Required: authUrl (default https://lon.identity.api.rackspacecloud.com/v2.0), username, apiKey, region (default LON), container. Optional: tempUrlKey, cdnEnabled (default true), fetch, tokenCache, headers, retry.
  • ADDED: RackspaceAuthClient — handles v2 identity auth (POST /v2.0/tokens), token cache with expiry, automatic re-auth on 401. Exposes getStorageEndpoint() and getCdnEndpoint() resolved from the service catalog for the configured region.
  • ADDED: RackspaceCdnClient — enableCdn(container), getCdnMetadata(container) (parses X-Cdn-Uri, X-Cdn-Ssl-Uri, X-Ttl, X-Cdn-Enabled), purgeObject(container, key) (DELETE on /v1/services/cdn/<container>/<key> with X-Purge-Email honoured if provided).
  • ADDED: RackspaceTempUrl — HMAC-SHA1 TempURL signer per the Swift TempURL middleware spec: signed string <METHOD>\n<EXPIRES>\n<PATH> with the account temp-url-key (or container-scoped key). Optional temp-url-prefix mode.
  • ADDED: STORAGE_CDN_RACKSPACE_CODES — 9 codes: AUTH_FAILED, TOKEN_EXPIRED, CONTAINER_NOT_FOUND, CDN_NOT_ENABLED, TEMP_URL_KEY_MISSING, PURGE_RATE_LIMITED, REGION_NOT_IN_CATALOG, INVALID_OPTIONS, UPSTREAM_ERROR. StorageCdnRackspaceError class with container/region/httpStatus discriminants.
  • ADDED: Capabilities — partialReads: true (Swift supports Range), nativeStreams: true, partialWrites: false, atomicWrites: false, contentAddressable: false; cdn: { cdnPurge: true, cdnPrefixPurge: false, cdnSignedUrls: true, cdnOriginUploads: true }.
  • ADDED: Module-augmentation registry — declare module "@suaveplan/storage" adds rackspace-cdn: RackspaceCdnAdapter slot; registerRackspaceCdnProvider() factory hook (idempotent).
  • ADDED: @suaveplan/retry integration on transient (429, 5xx, network), with Retry-After honoured.
  • ADDED: OTel surface — same shape as storage-http (storage_cdn_rackspace.op_duration_ms, …io_bytes, …reads_total, …writes_total, …purges_total, …errors_total), with extra provider="rackspace" attribute.

Out of scope

  • Non-LON regions. The package supports the full region catalog (LON, IAD, DFW, ORD, SYD, HKG) — region is just a config field — but the test fixture is LON-only because that is the user's deployment. Other regions verified by unit tests against a mocked service catalog response.
  • v1 auth. Rackspace v1 auth (X-Auth-User/X-Auth-Key against auth.api.rackspacecloud.com) is deprecated; this package targets v2 identity exclusively.
  • CDN log delivery. Cloud Files CDN log retrieval API is operations tooling; out of scope.
  • Bulk operations. Swift bulk-delete and bulk-upload are not exposed via AbstractStorage. Callers loop.
  • Static Web hosting headers (X-Container-Meta-Web-Index, …-Error). Belong in deployment tooling, not the adapter.

Source mapping

Genesis-original. References:

Requirements with no source line

Requirement Justification
Token cache with proactive refresh Auth tokens expire (24h default). Without a cache, every request re-auths. Proactive refresh (5 min before expiry) avoids the cliff where in-flight requests start 401-ing.
Service-catalog lookup vs. hard-coded endpoints Rackspace returns the canonical region endpoint in the auth response. Hard-coding LON URLs would break for IAD/DFW/etc. and silently misroute if Rackspace renumbers.
TempURL key stored in adapter options, not fetched per-call Reading the container or account meta on every signUrl would be wasteful. Caller provides the key (rotated via Rackspace control panel) at construction.
cdnPrefixPurge: false Rackspace CDN purge API is per-object. Prefix purge requires listing + per-key calls (rate-limited at ~25/min). Honest declaration.

Verification approach

  • src/rackspace-cdn-adapter/rackspace-cdn-adapter.test.ts — CRUD via injected FetchLike, range reads, signed-URL round-trip, purge happy path, READ_ONLY on list if container too large.
  • src/rackspace-cdn-adapter/rackspace-cdn-adapter.spec-conformance.test.ts — instanceof CdnAdapter + AbstractStorage, capabilities matrix, every STORAGE_CDN_RACKSPACE code reachable.
  • src/auth-client/auth-client.test.ts — v2 auth round-trip, token cache hit/miss, proactive refresh under FakeClock, service-catalog lookup for LON / IAD.
  • src/cdn-client/cdn-client.test.ts — enableCdn, getCdnMetadata parsing, purgeObject happy path, 429 retry with Retry-After.
  • src/temp-url/temp-url.test.ts — HMAC-SHA1 signature byte-equal to Swift reference vectors, temp-url-prefix mode, expired-signature rejected.
  • src/registry/registry.test.ts — createStorage({ type: "rackspace-cdn", config }) returns RackspaceCdnAdapter, idempotent re-registration.

Impact

Affected dependents

  • No dependents yet (bunx turbo ls --filter=...@suaveplan/storage-cdn-rackspace).

Affected upstreams

  • @suaveplan/storage-cdn — StorageProviderRegistry module-augmented with rackspace-cdn slot. Additive. [x] no action needed.
  • Hard dependency on @suaveplan/storage-cdn (must be archived first).