74 lines
6.6 KiB
Markdown
74 lines
6.6 KiB
Markdown
# 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:
|
|
|
|
- OpenStack Swift API v1: <https://docs.openstack.org/api-ref/object-store/>
|
|
- Rackspace Cloud Files developer guide (TempURL, CDN enablement)
|
|
- Rackspace identity v2: <https://docs.rackspace.com/docs/cloud-identity-v2>
|
|
|
|
### 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).
|