gateway

package
v0.2.3 Latest Latest
Warning

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

Go to latest
Published: Jul 2, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

Documentation

Overview

Package gateway runs the in-cluster Kubernetes Gateway API controller: it watches GatewayClass / Gateway / HTTPRoute objects, translates the HTTP routing they describe into a Cadishfile, and hot-swaps cadish's live routing through Server.ApplyConfig — the SAME atomic swap the Ingress controller (internal/ingress) uses (D55/D58). It runs ALONGSIDE the Ingress controller, mirroring its architecture: a pure translator, a debounced reconcile, per-resource graceful degradation, a leader-elected status writer, and Kubernetes status conditions instead of Events for accept/reject feedback.

SLICE 1 (this package) delivers: GatewayClass acceptance, Gateway HTTP listeners, HTTPRoute host + path (Exact / PathPrefix) routing to a Service backendRef, and the Accepted / Programmed / ResolvedRefs status conditions. DEFERRED to slice 2 (clear seams + TODOs left in place): HTTPS/TLS listeners + certificateRefs, header/query matchers, cross-namespace ReferenceGrant, and backendRef weights/filters.

HTTPRoute filters are currently NOT implemented — none of them. Every filter (RequestHeaderModifier, RequestRedirect, URLRewrite, RequestMirror, …) yields an UnsupportedValue reject and the rule is STILL served WITHOUT the filter applied (fail-open): e.g. a RequestHeaderModifier meant to strip an inbound Authorization header does not run, and the backend is served as if the filter were absent. Implementing the filters — and deciding the serve-vs-block policy for a security-relevant filter (reject the whole rule vs. serve it un-filtered) — is tracked as CAD-12. Do not read the "supported cheaply" phrasing of older comments as truth: it never matched the code.

Index

Constants

View Source
const ControllerName gatewayv1.GatewayController = "cadi.sh/gateway-controller"

ControllerName is the value a GatewayClass's spec.controllerName MUST carry for cadish to own it (the GatewayClass → controller binding). It mirrors the Ingress controller's IngressClass controller name (ingress.ControllerName = "cadi.sh/ingress-controller").

Variables

This section is empty.

Functions

func OwnsClass

func OwnsClass(gc *gatewayv1.GatewayClass) bool

OwnsClass reports whether this controller owns gc: its spec.controllerName must equal ControllerName. A GatewayClass naming a DIFFERENT controller is ignored entirely (no status is written for it — another controller owns it).

Types

type Applier

type Applier interface {
	ApplyConfig(*config.Config) error
}

Applier is the swap seam the controller drives: *server.Server satisfies it via Server.ApplyConfig (the SAME atomic routing swap the Ingress controller uses).

type Config

type Config struct {
	// Namespaces, when non-empty, restricts which namespaces' Gateways/HTTPRoutes are
	// served (informers still watch cluster-wide; reconcile filters). Empty ⇒ all.
	Namespaces []string
	// ResyncDebounce is the quiet window after the last watched change before a reconcile
	// (default 250ms).
	ResyncDebounce time.Duration
	// Kubeconfig is the explicit kubeconfig path for the resolver client (out-of-cluster).
	Kubeconfig string
	// LeaderElection enables the leader-elected status writer. When false the writer runs
	// unconditionally (single-replica / tests). Mirrors the Ingress controller; serving is
	// never gated by leadership.
	LeaderElection  bool
	LeaderNamespace string
	LeaderName      string
	// Identity uniquely names this replica for leader election (e.g. the pod name).
	Identity string
}

Config tunes the Gateway controller.

type Controller

type Controller struct {
	// contains filtered or unexported fields
}

Controller watches GatewayClass/Gateway/HTTPRoute (+ Layer-1 EndpointSlices), debounces change events, re-renders the Cadishfile and applies it via Applier. A bad render never takes serving down: the last good config stays live. Per-resource graceful degradation mirrors the Ingress controller (a bad HTTPRoute does not break others). Status is written back as Kubernetes conditions by a leader-elected writer (serving never gated).

func New

func New(cs kubernetes.Interface, gwcs gwclient.Interface, applier Applier, base string, opts Config) *Controller

New builds a Controller. cs is the core clientset (Layer-1 resolver + status writes need it; the gateway-api objects come from gwcs). applier is the live server; base is the globals-only Cadishfile (cache/admin/tls defaults — sites come from HTTPRoutes).

func (*Controller) Run

func (c *Controller) Run(ctx context.Context) error

Run starts the informers, waits for cache sync, performs an initial reconcile, then reconciles on every debounced change until ctx is cancelled.

func (*Controller) SetLogger

func (c *Controller) SetLogger(l *slog.Logger)

SetLogger overrides the controller's logger (defaults to slog.Default()).

func (*Controller) Stats

func (c *Controller) Stats() Stats

Stats returns the current reconcile snapshot (safe for concurrent use).

func (*Controller) UpdateBase

func (c *Controller) UpdateBase(base string)

UpdateBase swaps the base (globals-only) Cadishfile and triggers a reconcile (SIGHUP).

type Inputs

type Inputs struct {
	Classes  []*gatewayv1.GatewayClass
	Gateways []*gatewayv1.Gateway
	Routes   []*gatewayv1.HTTPRoute
	Grants   []*gatewayv1.ReferenceGrant
	// contains filtered or unexported fields
}

Inputs is the snapshot the translator renders: the GatewayClasses this controller may own, the Gateways, the HTTPRoutes, and the ReferenceGrants that authorize cross-namespace references. The translator is PURE (deterministic, no I/O): identical inputs render byte-identical output, so the controller can skip no-op swaps.

secretUsable / certCovers inject the controller's TLS-Secret validation so the translator stays pure and unit-testable (mirroring ingress.TLSPlan):

  • secretUsable(ns, name) reports whether the kubernetes.io/tls Secret EXISTS and parses into a usable keypair (a present-but-corrupt Secret reports false);
  • certCovers(ns, name, host) reports whether that Secret's certificate SANs cover host (the F10 SAN-coverage gate).

Both may be nil (TLS off / older tests): then HTTPS listeners are acknowledged but not programmed (no BYO cert is registered).

type Reject

type Reject struct {
	Kind   string // "GatewayClass" | "Gateway" | "HTTPRoute"
	Object string // "ns/name" (or "name" for the cluster-scoped GatewayClass)
	Reason string
}

Reject records one element (GatewayClass / Gateway / HTTPRoute) that was skipped, with a reason. The controller turns rejects into status conditions; serving is unaffected.

func Translate

func Translate(in Inputs) (string, []Reject)

Translate emits the concatenated Cadishfile (sites only). Convenience wrapper.

type Result

type Result struct {
	Sites          []ingress.RenderedSite
	Rejects        []Reject
	AttachedRoutes map[string]int
	// AttachedRoutesByListener maps "gwKey\x00listenerName" → the number of routes attached
	// to THAT listener (honoring the listener's hostname / a route's sectionName scoping),
	// used for the per-listener status.attachedRoutes (GW2). The per-Gateway AttachedRoutes
	// total is NOT a correct per-listener count when listeners are hostname-scoped.
	AttachedRoutesByListener map[string]int
	AcceptedRoutes           map[string]bool
	ResolvedRoutes           map[string]bool
	// RefNotPermittedRoutes is the subset of routes whose ResolvedRefs failure was a
	// cross-namespace ref refused for lack of a ReferenceGrant (status reason
	// RefNotPermitted vs the generic BackendNotFound).
	RefNotPermittedRoutes map[string]bool
	// HostOwnedRoutes is the subset of routes rejected because (some of) their hosts'
	// ROUTING is owned by another namespace (Fix #3 cross-ns hijack guard). The status
	// writer reports Accepted=False with reason NotAllowedByListeners for a route that
	// retained no host. (A route that kept at least one host stays Accepted; this set is
	// still recorded for the per-host Event.)
	HostOwnedRoutes map[string]bool
	// ProgrammedGateways is the set of Gateway "ns/name" with at least one programmed
	// listener (an HTTP listener, or an HTTPS listener whose cert was registered).
	ProgrammedGateways map[string]bool
	// ProgrammedListeners maps "gwKey\x00listenerName" → true for each listener that is
	// Programmed (HTTP always; HTTPS only when its BYO cert was registered).
	ProgrammedListeners map[string]bool
	// ListenerRejects maps "gwKey\x00listenerName" → a reason when a listener was NOT
	// programmed (e.g. an HTTPS listener with a missing/mismatched cert), for per-listener
	// status conditions.
	ListenerRejects map[string]string
	// SecretRefs are the BYO TLS Secrets to load (deduped by ns/name, hosts unioned), for
	// the controller's Server.SetDynamicCerts injection — the SAME path Ingress uses.
	SecretRefs []SecretRef
	// TLSHosts is the set of hostnames cadish terminates TLS for (HTTPS listener hosts with
	// a registered cert), so the controller can drive the redirect/Programmed posture.
	TLSHosts []string
}

Result is what the translator produces: the rendered sites, per-element rejects, the status bookkeeping, and the BYO TLS Secret refs to inject via Server.SetDynamicCerts.

func TranslateResult

func TranslateResult(in Inputs) Result

TranslateResult is the full translation. SLICE 2 adds, over slice 1: HTTPS/TLS listeners with certificateRefs (BYO Secret termination, reusing the Ingress SetDynamicCerts path + the F10 SAN gate + host-ownership), advanced HTTPRoute matchers (headers / queryParams / method, AND within a match, OR across matches), cross-namespace refs gated by a ReferenceGrant, and weighted multi-backend pools. HTTPRoute filters are NOT implemented (none of them): each is surfaced with an UnsupportedValue reject and the rule still serves its backend WITHOUT the filter applied (fail-open — see CAD-12), rather than silently dropped.

type SecretRef

type SecretRef = ingress.SecretRef

SecretRef is one BYO TLS Secret to load (re-exported shape of ingress.SecretRef so the controller injects Gateway and Ingress certs through the SAME Server.SetDynamicCerts).

type Stats

type Stats struct {
	WatchedRoutes   int
	LastAppliedHash string
	Rejects         int
	LastError       string
	IsLeader        bool
}

Stats is a point-in-time snapshot of the controller's reconcile state.

type TLSInjector

type TLSInjector interface {
	SetDynamicCerts([]tlsacme.DynamicCert) error
}

TLSInjector is the OPTIONAL typed side-channel for BYO TLS Secret certs referenced by a Gateway HTTPS listener's certificateRefs. *server.Server satisfies it via SetDynamicCerts — the SAME hot-swap dynamic-cert mechanism the Ingress controller uses, so a Gateway BYO cert is registered exactly like an Ingress spec.tls Secret cert (one TLS manager, keyed by SNI host). A bare-Applier test fake that does not implement it simply disables BYO-Secret TLS for Gateway listeners (the listener is then acknowledged-but-not-programmed).

type Warmer added in v0.2.1

type Warmer interface {
	MarkWarm()
}

Warmer is the OPTIONAL seam the controller uses to flip the data plane's warm-readiness flag AFTER its FIRST successful reconcile builds the routing table from synced listers. *server.Server satisfies it via MarkWarm. Until marked warm the reserved /.cadish/readyz probe returns 503, so the pod's readiness/startup probe keeps Kubernetes from routing to a not-yet-reconciled pod (no rollout 404). A bare-Applier test fake that does not implement it simply has no warm gate. MarkWarm is idempotent, so calling it on every successful reconcile is safe.

Jump to

Keyboard shortcuts

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