Documentation
¶
Overview ¶
Package ferrflow is the HTTP client the operator uses to talk to a FerrFlow API instance. It's deliberately narrow: only the endpoints the reconciler actually needs, with sharp error typing so the controller can translate transport failures into `Ready=False` conditions without guessing.
Index ¶
- func IsAuthError(err error) bool
- func IsNotFound(err error) bool
- type APIError
- type AuthError
- type AuthKind
- type BulkRevealResponse
- type Client
- func (c *Client) BulkReveal(ctx context.Context, org, project, vaultName, namespace string, names []string) (*BulkRevealResponse, error)
- func (c *Client) IsClusterIdentity() bool
- func (c *Client) OIDCExchange(ctx context.Context, clusterID, saToken string) (string, time.Time, error)
- func (c *Client) Probe(ctx context.Context) error
- type NotFoundError
- type OIDCExchangeResponse
- type Option
- type RetryPolicy
- type TransportError
- type VaultSummary
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func IsAuthError ¶
IsAuthError reports whether the error represents a 401 or 403.
func IsNotFound ¶
IsNotFound reports whether the error represents a 404.
Types ¶
type APIError ¶
APIError covers any remaining non-2xx status. For 5xx responses it also records the total number of attempts made before the client gave up.
type AuthKind ¶
type AuthKind int
AuthKind distinguishes 401 (bad token) from 403 (missing scope). The operator handles them differently: 401 halts reconciliation until the Secret is updated, 403 signals a misconfiguration that won't fix itself.
const ( AuthForbidden )
type BulkRevealResponse ¶
type BulkRevealResponse struct {
Secrets map[string]string `json:"secrets"`
Missing []string `json:"missing"`
Vault VaultSummary `json:"vault"`
}
BulkRevealResponse is the decoded shape of `GET /orgs/:org/projects/:proj/vaults/by-name/:vault/secrets/reveal`.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a narrow FerrFlow HTTP client.
Zero value is not usable; construct via `New`.
func New ¶
New constructs a client targeting `baseURL` with the given bearer token. `baseURL` is the API root — e.g. `https://ferrflow.example.com`. The client adds `/api/v1/...` paths itself.
func (*Client) BulkReveal ¶
func (c *Client) BulkReveal( ctx context.Context, org, project, vaultName, namespace string, names []string, ) (*BulkRevealResponse, error)
BulkReveal returns the requested secrets from the named vault. When `names` is empty the server returns every secret in the vault. `namespace` is the Kubernetes namespace the request is made from — sent as `X-FerrFlow-Namespace` on every call. The API **requires** this header when the token is a cluster identity and ignores it otherwise. Returned `Missing` lists requested keys that weren't present — `Ready=False` worthy on the caller's CR.
func (*Client) IsClusterIdentity ¶
IsClusterIdentity reports whether the configured token is a cluster identity (`ffclust_...`) rather than a user API token (`fft_...`). The FerrFlow API's reveal endpoint enforces namespace-scoped authorization when the caller authenticates with a cluster identity, and requires the `X-FerrFlow-Namespace` header on every request.
func (*Client) OIDCExchange ¶
func (c *Client) OIDCExchange( ctx context.Context, clusterID, saToken string, ) (string, time.Time, error)
OIDCExchange posts an OIDC JWT (typically a projected ServiceAccount token) to FerrFlow and returns the minted short-lived cluster bearer + its expiry. The endpoint is unauthenticated — the JWT body IS the auth — so the `Authorization` header on the outbound request is irrelevant and we just set the dummy token the client was built with.
Used by the token broker. The returned bearer can be fed back into a new `ferrflow.New` client instance for downstream API calls.
func (*Client) Probe ¶
Probe is a lightweight reachability check against the FerrFlow API. Calls `GET <baseURL>/health` (public, unauthenticated) and succeeds when the response is 200 with a JSON body containing `{"status":"ok"}`.
Deliberately does not exercise the token — auth correctness is reported per-vault by the `FerrFlowSecret` reconciler, where the org/project/vault context is known. Probe answers the narrower question "can the operator reach this API instance at all?".
type NotFoundError ¶
type NotFoundError struct {
Message string
}
NotFoundError is returned for 404 responses (unknown org/project/vault).
func (*NotFoundError) Error ¶
func (e *NotFoundError) Error() string
type OIDCExchangeResponse ¶
type OIDCExchangeResponse struct {
AccessToken string `json:"access_token"`
ExpiresAt time.Time `json:"expires_at"`
TokenType string `json:"token_type"`
}
OIDCExchangeResponse is the decoded shape of `POST /clusters/oidc-exchange`.
type Option ¶
type Option func(*Client)
Option configures a Client at construction time.
func WithRetry ¶
func WithRetry(p RetryPolicy) Option
WithRetry overrides the retry policy. Pass `RetryPolicy{MaxAttempts: 1}` to disable retries entirely — useful in tests that want to assert a single request was made.
type RetryPolicy ¶
type RetryPolicy struct {
MaxAttempts int
Backoff []time.Duration
// Jitter is the fractional ± range applied to each backoff delay. 0.25
// picks a multiplier uniformly in [0.75, 1.25]. Zero disables jitter.
Jitter float64
// contains filtered or unexported fields
}
RetryPolicy controls the bounded retry loop applied to every HTTP call the client makes. Retries cover `TransportError` and HTTP 5xx responses only — 4xx is returned immediately because those are caller-fixable (bad token, wrong vault name, etc.) and retrying them just wastes request budget.
The `Backoff` slice encodes the delay *before* each retry attempt, so `Backoff[0]` is waited before attempt #2, `Backoff[1]` before attempt #3, and so on. With `MaxAttempts: 3` only the first two entries are ever used, but the full schedule stays defined so raising the attempt cap later is a one-line change.
func DefaultRetryPolicy ¶
func DefaultRetryPolicy() RetryPolicy
DefaultRetryPolicy is the policy applied when callers don't override it: up to three attempts with 100ms / 400ms / 1.6s delays and ±25% jitter.
type TransportError ¶
TransportError wraps network-level failures (DNS, TCP, TLS, timeouts). The client retries these with backoff; `Attempts` records how many tries were spent before giving up (or, on success, how many the caller made before seeing the error — always ≥ 1).
func (*TransportError) Error ¶
func (e *TransportError) Error() string
func (*TransportError) Unwrap ¶
func (e *TransportError) Unwrap() error
type VaultSummary ¶
type VaultSummary struct {
ID string `json:"id"`
Name string `json:"name"`
Environment string `json:"environment"`
}
VaultSummary echoes the vault we resolved by name, handy for logging.