feat(client): add Go SDK for hypercache-server clusters#124
Merged
Conversation
Introduce username/password authentication via `Authorization: Basic` as a first-class credential class alongside static bearer tokens, mTLS certs, and OIDC JWTs. The resolve chain is now: bearer → Basic → mTLS → OIDC. Changes: - pkg/httpauth/policy.go: new `BasicIdentity` struct (bcrypt-hashed passwords); `resolveBasic` resolver with fail-closed TLS posture (`AllowBasicWithoutTLS` defaults to false); `Capabilities()` on `Identity` derives stable `cache.<scope>` strings from scopes. - pkg/httpauth/loader.go: new `users:` YAML block in `fileSchema`; `AllowBasicWithoutTLS` config flag; boot-time `Validate()` rejects malformed bcrypt hashes and empty usernames loudly. - cmd/hypercache-server/main.go + openapi.yaml: `meResponse` and `IdentityResponse` gain a required `capabilities` field populated from `Identity.Capabilities()`; OpenAPI description updated to reflect all four auth modes. - __examples/distributed-oidc-client/: new runnable OIDC client-credentials demo (PUT/GET/DELETE/batch surface) with full README covering env-var setup, scope mapping patterns across Keycloak/Auth0/Okta, and production caveats. - docs/rfcs/0003-client-sdk-and-redis-style-affordances.md: new RFC proposing a Go client SDK with multi-endpoint HA, username/password auth, and structured typed errors; design options enumerated with recommended path. - Tests: Basic auth happy path, wrong-password/user, malformed header, and plaintext-refused-by-default cases in `policy_test.go`; YAML round-trip and boot-guard tests in `loader_test.go`; `me_test.go` updated for the new `capabilities` field. - go.mod: promote `golang.org/x/crypto` from indirect to direct; bump ewrap v1.5.0 → v1.5.1, sectools v1.2.5 → v1.2.6.
Introduce pkg/client — a typed Go client for hypercache-server that closes
three operational gaps surfaced by the OIDC-client example:
- Multi-endpoint HA: New() accepts a seed slice; each request picks an
endpoint at random and falls back to the next on transport failure, 5xx,
or 503 draining. 4xx answers are deterministic and do not trigger failover.
- Optional topology refresh: WithTopologyRefresh(interval) runs a background
loop that pulls /cluster/members and updates the in-memory endpoint view,
so nodes added or removed after deploy become reachable without redeploying
consumers. Seeds remain as a permanent fallback when the live view empties.
- Four auth modes in one API: WithBearerAuth, WithBasicAuth,
WithOIDCClientCredentials (auto-refreshing OAuth2 client-credentials flow),
and WithHTTPClient (bring your own mTLS-configured client). Mutually
exclusive; last applied wins.
Typed error surface: ErrNotFound, ErrUnauthorized, ErrForbidden, ErrDraining,
ErrBadRequest, ErrInternal, ErrAllEndpointsFailed, ErrNoEndpoints compose with
errors.Is. *StatusError carries the canonical {code, error, details} envelope
for callers that need finer discrimination via errors.As.
Commands: Set, Get (raw bytes), GetItem (full envelope with version/owners),
Delete, Identity (/v1/me canary), Endpoints, RefreshTopology, Close.
Promote golang.org/x/oauth2 from indirect to direct dependency. Update
cspell.config.yaml with new terms (errchkjson, httptest, mathrand, misrouted,
unparseable). Full test coverage in pkg/client/client_test.go covering happy-
path round-trip, every auth mode, 5xx failover, 4xx no-failover regression
guard, exhaustive-failure wrapping, all sentinel errors.Is mappings, topology
refresh, and constructor input validation.
Split the distributed-oidc-client example into two variants: - distributed-oidc-client/ — rewritten as the recommended ~150-line SDK consumer using pkg/client with OIDC client-credentials, multi-endpoint failover, and topology refresh (down from ~480 lines) - distributed-oidc-client-raw/ — the original hand-rolled net/http version retained as a wire-protocol reference and for non-Go consumers Add docs/client-sdk.md: comprehensive SDK reference covering all four auth modes (bearer, Basic, OIDC client credentials, mTLS via WithHTTPClient), failover policy, topology refresh semantics with the 1s floor and seed fallback, the full sentinel + *StatusError recipe set, command surface, and production caveats (connection pooling, retry policy, OTel propagation, OIDC refresh visibility). Update __examples/README.md to list both variants with the SDK path flagged as recommended, and register the new page in mkdocs.yml Reference navigation.
Introduce three new methods on *Client that execute their respective
operations in a single HTTP round-trip against the existing
POST /v1/cache/batch/{put,get,delete} wire endpoints.
Key design points:
- Per-item granularity: the outer error fires only on transport/auth/
HTTP-level failures; individual item failures surface via the result's
Err field (*StatusError), preserving errors.Is/errors.As compatibility
with the single-key sentinel set.
- BatchGet treats missing keys as Found=false, not an error.
- Empty input is a client-side no-op (returns empty slice, nil error)
without dispatching an HTTP request.
- Results mirror input order (index i of result = outcome for input i).
Supporting changes:
- Extract contentTypeJSON const to eliminate drift between call sites.
- Add test_consts_test.go with shared JSON wire-key constants.
- Eight new test cases in batch_test.go covering happy paths, per-item
failures, mixed found/missing, empty-input no-ops, and HTTP-level
failure wrapping ErrAllEndpointsFailed.
- Extend docs/client-sdk.md with a dedicated Batch operations section
and update the commands reference table with the three new methods.
- Add a BatchSet demo step to the distributed-oidc-client example.
- Fix markdown anchor links in docs/oncall.md (double-dash → single-dash).
- Bump softprops/action-gh-release v2 → v3 in release.yml.
Switch rebalance integration tests from using node.Set (quorum write) to node.DebugInject (test bypass) when seeding keys before triggering topology changes. This eliminates a ~1-in-50 flake under -shuffle in CI caused by fan-out transport calls missing deadlines on brand-new two-node clusters. Add populateKeysOnAll helper to inject keys across multiple nodes simultaneously, and migrate leave, replica-diff, and throttle tests to use the new helper instead of inline key-seeding loops.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduce pkg/client — a typed Go client for hypercache-server that closes
three operational gaps surfaced by the OIDC-client example:
Multi-endpoint HA: New() accepts a seed slice; each request picks an
endpoint at random and falls back to the next on transport failure, 5xx,
or 503 draining. 4xx answers are deterministic and do not trigger failover.
Optional topology refresh: WithTopologyRefresh(interval) runs a background
loop that pulls /cluster/members and updates the in-memory endpoint view,
so nodes added or removed after deploy become reachable without redeploying
consumers. Seeds remain as a permanent fallback when the live view empties.
Four auth modes in one API: WithBearerAuth, WithBasicAuth,
WithOIDCClientCredentials (auto-refreshing OAuth2 client-credentials flow),
and WithHTTPClient (bring your own mTLS-configured client). Mutually
exclusive; last applied wins.
Typed error surface: ErrNotFound, ErrUnauthorized, ErrForbidden, ErrDraining,
ErrBadRequest, ErrInternal, ErrAllEndpointsFailed, ErrNoEndpoints compose with
errors.Is. *StatusError carries the canonical {code, error, details} envelope
for callers that need finer discrimination via errors.As.
Commands: Set, Get (raw bytes), GetItem (full envelope with version/owners),
Delete, Identity (/v1/me canary), Endpoints, RefreshTopology, Close.
Promote golang.org/x/oauth2 from indirect to direct dependency. Update
cspell.config.yaml with new terms (errchkjson, httptest, mathrand, misrouted,
unparseable). Full test coverage in pkg/client/client_test.go covering happy-
path round-trip, every auth mode, 5xx failover, 4xx no-failover regression
guard, exhaustive-failure wrapping, all sentinel errors.Is mappings, topology
refresh, and constructor input validation