ferrflow

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 21, 2026 License: MPL-2.0 Imports: 11 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsAuthError

func IsAuthError(err error) bool

IsAuthError reports whether the error represents a 401 or 403.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether the error represents a 404.

Types

type APIError

type APIError struct {
	Status   int
	Message  string
	Attempts int
}

APIError covers any remaining non-2xx status. For 5xx responses it also records the total number of attempts made before the client gave up.

func (*APIError) Error

func (e *APIError) Error() string

type AuthError

type AuthError struct {
	Kind    AuthKind
	Message string
}

AuthError is returned for 401/403 responses.

func (*AuthError) Error

func (e *AuthError) Error() string

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 (
	AuthUnauthorized AuthKind = iota
	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

func New(baseURL, token string, opts ...Option) (*Client, error)

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

func (c *Client) IsClusterIdentity() bool

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

func (c *Client) Probe(ctx context.Context) error

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

type TransportError struct {
	Underlying error
	Attempts   int
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL