controller

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: 29 Imported by: 0

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

View Source
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

View Source
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

func ObserveReconcile(begin time.Time, result string)

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

func SetConnectionReady(namespace, name string, ready bool)

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

type ClientFactory func(baseURL, token string) (ferrflowClient, error)

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

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

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

Jump to

Keyboard shortcuts

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