Documentation
¶
Overview ¶
Package controller — FerrFlowConnection reconciler.
Probes the configured FerrFlow API URL on a timer and reflects the outcome in `status.conditions[type=Ready]` + `status.lastCheckedAt`. Useful on its own (users debugging a freshly-applied connection can `kubectl get ffc` without creating a FerrFlowSecret first) and as a backstop — when the token Secret is rotated, the watch here re-reconciles immediately instead of waiting for the next scheduled poll.
Package controller holds the reconciliation loops for the operator's CRDs.
Index ¶
- Constants
- Variables
- func ApplyTransforms(input map[string]string, transforms []ffv1alpha1.SecretTransform) (map[string]string, error)
- func DeleteConnectionReady(namespace, name string)
- func DeleteLastSyncTimestamp(namespace, name string)
- func IncSyncError(reason string)
- func ObserveReconcile(begin time.Time, result string)
- func SetConnectionReady(namespace, name string, ready bool)
- func SetLastSyncTimestamp(namespace, name string)
- type ClientFactory
- type FerrFlowConnectionReconciler
- type FerrFlowSecretReconciler
- type TokenBroker
- type TransformError
Constants ¶
const ( TransformPrefix = "prefix" TransformSuffix = "suffix" TransformRename = "rename" TransformBase64Decode = "base64Decode" TransformJSONExpand = "jsonExpand" )
Transform kind constants. Kept as typed strings rather than an iota enum because they're also the wire format the user writes in YAML — any drift between code and spec would be a silent breakage.
Variables ¶
var ( // SyncDuration records wall-clock reconcile latency for FerrFlowSecret, // partitioned by outcome. Default buckets are fine: reconciles are // dominated by one HTTP round-trip to the FerrFlow API, not sub-ms work. SyncDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "ferrflow_secret_sync_duration_seconds", Help: "Duration of FerrFlowSecret reconciles, labelled by result (success/failure).", Buckets: prometheus.DefBuckets, }, []string{"result"}, ) // SyncErrors counts failed reconciles by the Reason stamped on the Ready // condition — same vocabulary users already see in `kubectl describe`, so // alerts and dashboards line up with CR status. SyncErrors = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "ferrflow_secret_sync_errors_total", Help: "FerrFlowSecret reconcile failures, labelled by reason.", }, []string{"reason"}, ) // LastSyncTimestamp is the unix time of the most recent successful sync // per CR. Useful for "nothing synced in the last hour" style alerts. LastSyncTimestamp = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "ferrflow_secret_last_sync_timestamp_seconds", Help: "Unix timestamp of the last successful FerrFlowSecret sync.", }, []string{"namespace", "name"}, ) // ConnectionReady is 1 when a FerrFlowConnection's Ready condition is // True, 0 otherwise. ConnectionReady = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "ferrflow_connection_ready", Help: "Whether a FerrFlowConnection is Ready (1) or not (0).", }, []string{"namespace", "name"}, ) )
Custom Prometheus collectors for the FerrFlow controllers. Registered with controller-runtime's shared registry so they show up on the manager's existing :8080/metrics endpoint — no separate HTTP server needed.
Functions ¶
func ApplyTransforms ¶
func ApplyTransforms(input map[string]string, transforms []ffv1alpha1.SecretTransform) (map[string]string, error)
ApplyTransforms mutates a fresh copy of `data` through each transform in order and returns the resulting map. The input is not modified. On error, the map returned is the partial state at the failing step — callers are expected to surface the error rather than write the partial result.
Semantics (kept deliberately conservative for the MVP):
- Renaming a missing key is a no-op, not an error. FerrFlow vaults are user-editable; failing the sync just because a key was removed upstream would make transforms fragile.
- Prefix / suffix apply to every current key. Running twice stacks.
- base64Decode with no keys decodes every value. With keys, only those listed; missing keys are ignored.
- jsonExpand drops the source key and emits `<KEY>_<SUB>` entries. Nested objects produce underscore-joined names, upper-cased. Array values are marshalled back to JSON (opinionated: we don't invent an index notation for the MVP).
func DeleteConnectionReady ¶
func DeleteConnectionReady(namespace, name string)
DeleteConnectionReady drops the gauge series for a deleted connection.
func DeleteLastSyncTimestamp ¶
func DeleteLastSyncTimestamp(namespace, name string)
DeleteLastSyncTimestamp drops the series for a CR that no longer exists, so the gauge doesn't leak labels forever.
func IncSyncError ¶
func IncSyncError(reason string)
IncSyncError bumps the failure counter for the given reason.
func ObserveReconcile ¶
ObserveReconcile records the elapsed time since `begin` under the given result label. Intended for use in a deferred closure where the caller flips `result` between "success" and "failure" as branches return.
func SetConnectionReady ¶
SetConnectionReady sets the ready gauge to 1 or 0.
func SetLastSyncTimestamp ¶
func SetLastSyncTimestamp(namespace, name string)
SetLastSyncTimestamp stamps the current time on the per-CR gauge.
Types ¶
type ClientFactory ¶
ClientFactory builds a ferrflowClient for a given base URL and token. Swapped out in unit tests to return a fake.
type FerrFlowConnectionReconciler ¶
type FerrFlowConnectionReconciler struct {
client.Client
Scheme *runtime.Scheme
// Broker resolves the bearer for the connection's configured auth
// mode. Shared with the FerrFlowSecret reconciler so the OIDC cache
// isn't split. Defaults to a fresh broker if nil.
Broker *TokenBroker
}
FerrFlowConnectionReconciler reports whether a FerrFlowConnection's configured FerrFlow API instance is reachable and whether the referenced token source (Secret or OIDC exchange) is usable.
func (*FerrFlowConnectionReconciler) Reconcile ¶
func (r *FerrFlowConnectionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)
Reconcile probes the connection and updates status. Errors are captured in the Ready condition rather than returned, so controller-runtime doesn't enter a tight retry loop on a long-lived misconfiguration.
func (*FerrFlowConnectionReconciler) SetupWithManager ¶
func (r *FerrFlowConnectionReconciler) SetupWithManager(mgr ctrl.Manager) error
SetupWithManager wires the reconciler and adds a watch on the referenced token Secrets so updates flow through immediately. Matches the pattern users expect — rotating the token in the Secret reconciles within seconds, not when the ten-minute probe tick lands.
type FerrFlowSecretReconciler ¶
type FerrFlowSecretReconciler struct {
client.Client
Scheme *runtime.Scheme
// DefaultRefreshInterval is used when the spec leaves `refreshInterval`
// blank or set to an unparseable value. Set by cmd/main.go at startup.
DefaultRefreshInterval time.Duration
// ClientFactory builds the FerrFlow HTTP client. Left nil in production —
// the reconciler falls back to defaultClientFactory, which hands back a
// real *ferrflow.Client. Unit tests inject a fake here.
ClientFactory ClientFactory
// Broker resolves the bearer for the connection's configured auth
// mode. Shared with the Connection reconciler so the OIDC cache isn't
// split across controllers. Defaults to a fresh broker if nil — tests
// leave it unset and rely on a per-test fake.
Broker *TokenBroker
}
FerrFlowSecretReconciler reconciles a FerrFlowSecret object against its upstream source (a vault in the FerrFlow API) and a downstream sink (a Kubernetes Secret in the same namespace).
func (*FerrFlowSecretReconciler) Reconcile ¶
func (r *FerrFlowSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)
Reconcile is the entrypoint called by controller-runtime whenever an event (create/update) fires on a `FerrFlowSecret` or on any object the controller explicitly watches (the referenced Secret and Connection).
func (*FerrFlowSecretReconciler) SetupWithManager ¶
func (r *FerrFlowSecretReconciler) SetupWithManager(mgr ctrl.Manager) error
SetupWithManager wires the reconciler into the controller manager and sets up the watches it needs.
Beyond the trivial `For` / `Owns` pair, we watch:
- `FerrFlowConnection` — a connection update (spec change or status flip) is effectively a cache-invalidation event for every FerrFlowSecret referencing it. Without this watch, the downstream CRs only re-reconcile on their `refreshInterval` (1h by default).
- `Secret` — specifically the token Secret that a referenced Connection points at. Rotating the token otherwise takes up to `refreshInterval` to flow through, which is too long for emergency rotation.
Both map funcs are bounded: each event triggers O(N) CR lookups where N is the number of CRs in the namespace, not cluster-wide. Field indexers keep those lookups cheap.
type TokenBroker ¶
type TokenBroker struct {
Client client.Client
// ReadToken is the filesystem read for the projected SA token. Set to
// nil to use the default (`os.ReadFile`).
ReadToken tokenReader
// Exchange is the HTTP POST to FerrFlow's `/clusters/oidc-exchange`.
// Set to nil to use the real ferrflow.Client. Tests inject fakes.
Exchange oidcExchanger
// contains filtered or unexported fields
}
TokenBroker resolves bearer tokens for FerrFlowConnections. Safe for concurrent use by both reconcilers.
The OIDC cache is keyed by `(namespace, connection name)` — two connections in different namespaces that happen to share a cluster ID still get separate cache entries because they read separate SA tokens (namespace-scoped ServiceAccounts).
func NewTokenBroker ¶
func NewTokenBroker(c client.Client) *TokenBroker
NewTokenBroker builds a broker wired to the real filesystem and FerrFlow HTTP client. Test code should construct `TokenBroker{...}` directly and fill the `ReadToken` / `Exchange` hooks.
func (*TokenBroker) Invalidate ¶
func (b *TokenBroker) Invalidate(namespace, name string)
Invalidate drops the cache entry for a connection. Called when the connection is deleted or its OIDC config changes, so stale bearers don't outlive the config that authorised them.
func (*TokenBroker) TokenFor ¶
func (b *TokenBroker) TokenFor( ctx context.Context, conn *ffv1alpha1.FerrFlowConnection, ) (string, error)
TokenFor resolves the bearer for a connection. Called on every reconcile that needs to hit the FerrFlow API.
Returns an error when:
- Neither `tokenSecretRef` nor `oidc` is set (spec validation failure).
- Both are set (spec validation failure — we refuse to guess).
- The configured mode fails to produce a token (Secret missing, SA token file missing, exchange rejected).
type TransformError ¶
type TransformError struct {
// Index is the zero-based position of the offending transform in
// spec.transforms. -1 means the error isn't associated with a single
// entry (e.g. output-key collision across transforms).
Index int
// Type is the transform kind that failed, for the user-facing message.
Type string
// Msg is the human-readable explanation. No secret values.
Msg string
}
TransformError is returned by ApplyTransforms when the CR's transforms list can't be applied cleanly. It's shaped so the reconciler can stamp it on the Ready condition (reason `TransformError`) without reaching for string matching.
func (*TransformError) Error ¶
func (e *TransformError) Error() string