controller

package
v0.2.0-alpha.3 Latest Latest
Warning

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

Go to latest
Published: May 7, 2026 License: Apache-2.0 Imports: 36 Imported by: 0

Documentation

Overview

Package controller provides Kubernetes reconciliation controllers for cfgate CRDs.

cfgate is a Gateway API-native Kubernetes operator for Cloudflare Tunnel, DNS, and Access management. This package contains the core reconciliation logic that synchronizes Kubernetes resources with Cloudflare APIs.

Controllers

The package provides five reconcilers:

  • CloudflareTunnelReconciler: Manages CloudflareTunnel CRD lifecycle including tunnel creation/adoption, cloudflared deployment, and ingress configuration sync.

  • CloudflareDNSReconciler: Manages CloudflareDNS CRD lifecycle including hostname collection from Gateway API routes and DNS record synchronization.

  • CloudflareAccessPolicyReconciler: Manages CloudflareAccessPolicy CRD lifecycle including Access Application creation and policy synchronization.

  • GatewayReconciler: Validates Gateway resources that reference CloudflareTunnel via the cfgate.io/tunnel-ref annotation.

  • HTTPRouteReconciler: Validates HTTPRoute resources against parent Gateways, resolves backend Services, and validates CloudflareAccessPolicy references.

Architecture

Controllers follow the controller-runtime reconciliation pattern:

  1. Fetch the primary resource
  2. Handle deletion via finalizers
  3. Execute reconciliation phases
  4. Update status with conditions
  5. Emit events for significant state changes

Each controller uses [GenerationChangedPredicate] to prevent reconciliation loops caused by status-only updates.

Logging

Controllers use the logr interface from controller-runtime:

log := log.FromContext(ctx).WithName("controller").WithName("<type>")

Log levels follow Kubernetes conventions:

  • Info (0): Reconciliation start/end, major state transitions
  • V(1): Intermediate steps, diagnostic information
  • Error: Failures with context for debugging

Events

Controllers emit Kubernetes events via events.EventRecorder:

  • Normal: Successful operations (Created, Reconciled, Synced)
  • Warning: Recoverable errors, deprecation warnings

Feature Gates

Optional Gateway API CRD support is controlled by features.FeatureGates:

  • ReferenceGrant: Required for cross-namespace policy attachment

Controllers check feature gates before registering watches to avoid startup failures when optional CRDs are not installed.

Subpackages

The controller package has several subpackages:

  • annotations: Annotation key constants and parsing utilities
  • context: Context wrappers for passing reconciliation state
  • features: FeatureGates for optional CRD detection
  • status: Condition builders and status utilities

Index

Constants

View Source
const (
	AccessPolicyControllerName = "cfgate.io/cloudflare-access-controller"
)
View Source
const (
	// GatewayControllerName is the controller name for GatewayClass.
	GatewayControllerName = "cfgate.io/cloudflare-tunnel-controller"
)

Variables

View Source
var AccessPolicyReferenceChangedPredicate = predicate.ResourceVersionChangedPredicate{}

AccessPolicyReferenceChangedPredicate passes policy updates that can affect applications referencing the policy, including status-only readiness changes.

View Source
var CfgateAnnotationOrGenerationPredicate = predicate.Or(
	predicate.GenerationChangedPredicate{},
	predicate.Funcs{
		UpdateFunc: func(e event.UpdateEvent) bool {
			changed := cfgateAnnotationsChanged(
				e.ObjectOld.GetAnnotations(),
				e.ObjectNew.GetAnnotations(),
			)
			if changed {
				predicateLog.V(1).Info("cfgate annotation change detected",
					"namespace", e.ObjectNew.GetNamespace(),
					"name", e.ObjectNew.GetName(),
					"kind", e.ObjectNew.GetObjectKind().GroupVersionKind().Kind,
				)
			}
			return changed
		},
	},
)

CfgateAnnotationOrGenerationPredicate passes events when:

  1. metadata.generation changed (spec change), OR
  2. Any cfgate.io/* annotation changed (value added, removed, or modified)

This replaces GenerationChangedPredicate on cross-resource watchers where cfgate annotations (cfgate.io/tunnel-ref, cfgate.io/origin-protocol, etc.) are meaningful triggers that don't increment generation.

For CRDs with status subresource, annotation-only changes do NOT increment metadata.generation. GenerationChangedPredicate alone would filter these events, causing the DNS controller to miss annotation additions post-Gateway-creation.

Unlike AnnotationChangedPredicate (which fires on ANY annotation change including Helm, Kiali, kubectl metadata), this predicate only checks cfgate.io/* prefixed annotations for more precise filtering.

View Source
var GatewayCreateAnnotationFilter = predicate.Funcs{
	CreateFunc: func(e event.CreateEvent) bool {
		for k := range e.Object.GetAnnotations() {
			if strings.HasPrefix(k, annotations.AnnotationPrefix) {
				return true
			}
		}
		return false
	},
	UpdateFunc:  func(e event.UpdateEvent) bool { return true },
	DeleteFunc:  func(e event.DeleteEvent) bool { return true },
	GenericFunc: func(e event.GenericEvent) bool { return true },
}

GatewayCreateAnnotationFilter rejects Gateway Create events that lack cfgate annotations. Use this on Gateway watches (AND-combined with CfgateAnnotationOrGenerationPredicate) to prevent "poisoned first reconcile" where a bare Gateway Create triggers unnecessary reconciliation that finds 0 hostnames.

This predicate only filters Create events. Update, Delete, and Generic events pass through unconditionally. This ensures annotation-add Updates still trigger reconciliation normally.

View Source
var GenerationOrDeletionPredicate = predicate.Or(
	predicate.GenerationChangedPredicate{},
	predicate.Funcs{
		UpdateFunc: func(e event.UpdateEvent) bool {
			if e.ObjectOld == nil || e.ObjectNew == nil {
				return false
			}

			if e.ObjectOld.GetDeletionTimestamp() == nil && e.ObjectNew.GetDeletionTimestamp() != nil {
				predicateLog.V(1).Info("deletion timestamp detected",
					"namespace", e.ObjectNew.GetNamespace(),
					"name", e.ObjectNew.GetName(),
				)
				return true
			}
			return false
		},
	},
)

GenerationOrDeletionPredicate passes events when:

  1. metadata.generation changed (spec change), OR
  2. DeletionTimestamp was just set (object marked for deletion)

This replaces GenerationChangedPredicate on For() clauses for CRDs that use finalizers. Without this, setting DeletionTimestamp (which does NOT increment generation) produces an Update event that GenerationChangedPredicate filters. The reconciler never sees the deletion until a stale RequeueAfter timer fires potentially minutes later, causing delayed or failed cleanup.

View Source
var TunnelIDChangedPredicate = predicate.Funcs{
	CreateFunc: func(e event.CreateEvent) bool {

		tunnel, ok := e.Object.(*cfgatev1alpha1.CloudflareTunnel)
		if !ok {
			return false
		}
		return tunnel.Status.TunnelID != ""
	},
	UpdateFunc: func(e event.UpdateEvent) bool {
		oldTunnel, ok := e.ObjectOld.(*cfgatev1alpha1.CloudflareTunnel)
		if !ok {
			return false
		}
		newTunnel, ok := e.ObjectNew.(*cfgatev1alpha1.CloudflareTunnel)
		if !ok {
			return false
		}
		return oldTunnel.Status.TunnelID != newTunnel.Status.TunnelID
	},
	DeleteFunc: func(e event.DeleteEvent) bool {
		return false
	},
}

TunnelIDChangedPredicate filters CloudflareTunnel events to only those where Status.TunnelID has changed. This is used by GatewayReconciler to detect when a tunnel has been created/adopted and the TunnelID becomes available.

Functions

This section is empty.

Types

type CloudflareAccessApplicationReconciler

type CloudflareAccessApplicationReconciler struct {
	client.Client
	Scheme   *runtime.Scheme
	Recorder events.EventRecorder

	CFClient        cloudflare.Client
	CredentialCache *cloudflare.CredentialCache
	FeatureGates    *features.FeatureGates
}

CloudflareAccessApplicationReconciler reconciles Gateway targets into Access Applications.

func (*CloudflareAccessApplicationReconciler) Reconcile

func (*CloudflareAccessApplicationReconciler) SetupWithManager

func (r *CloudflareAccessApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error

type CloudflareAccessPolicyReconciler

type CloudflareAccessPolicyReconciler struct {
	client.Client
	Scheme   *runtime.Scheme
	Recorder events.EventRecorder

	CFClient        cloudflare.Client
	CredentialCache *cloudflare.CredentialCache
	FeatureGates    *features.FeatureGates
}

CloudflareAccessPolicyReconciler reconciles reusable Cloudflare Access policies.

func (*CloudflareAccessPolicyReconciler) Reconcile

func (*CloudflareAccessPolicyReconciler) SetupWithManager

func (r *CloudflareAccessPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error

type CloudflareDNSReconciler

type CloudflareDNSReconciler struct {
	client.Client
	Scheme   *runtime.Scheme
	Recorder events.EventRecorder

	// APIReader provides uncached reads to avoid informer cache staleness
	// during hostname collection. Without this, r.List() may return stale
	// Gateway data when the annotation-add event that triggered reconciliation
	// hasn't propagated to the informer cache yet.
	APIReader client.Reader

	// CFClient is the Cloudflare API client. Injected for testing.
	CFClient cloudflare.Client

	// CredentialCache caches validated Cloudflare clients to avoid repeated validations.
	CredentialCache *cloudflare.CredentialCache
}

CloudflareDNSReconciler reconciles a CloudflareDNS object. It manages DNS records for CloudflareTunnel resources or external targets by watching Gateway API routes and syncing hostnames to Cloudflare DNS.

func (*CloudflareDNSReconciler) Reconcile

func (r *CloudflareDNSReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

Reconcile handles the reconciliation loop for CloudflareDNS resources. It collects hostnames from routes, resolves zones, and syncs DNS records.

The reconciliation proceeds through these phases:

  1. Fetch the CloudflareDNS resource
  2. Handle deletion via finalizers (cleanup DNS records)
  3. Resolve target (tunnel domain or external target)
  4. Collect hostnames from explicit config and Gateway routes
  5. Validate Cloudflare API credentials
  6. Resolve zone names to zone IDs
  7. Sync DNS records with conflict detection
  8. Verify ownership TXT records (if enabled)
  9. Update status conditions

On error, the controller requeues after 30 seconds. On success, it requeues after 5 minutes for periodic DNS sync.

func (*CloudflareDNSReconciler) SetupWithManager

func (r *CloudflareDNSReconciler) SetupWithManager(mgr ctrl.Manager) error

SetupWithManager sets up the controller with the Manager.

Watched resources:

  • CloudflareDNS (primary, with GenerationChangedPredicate)
  • CloudflareTunnel (via spec.tunnelRef, with GenerationChangedPredicate)
  • HTTPRoute (for hostname collection, with CfgateAnnotationOrGenerationPredicate)
  • Gateway (for tunnel reference, with CfgateAnnotationOrGenerationPredicate)

Uses GenerationChangedPredicate for CRD-only resources to prevent status-only reconciliation loops. Uses CfgateAnnotationOrGenerationPredicate on Gateway/HTTPRoute watchers where cfgate.io/* annotation changes (which don't increment generation on CRDs with status subresource) are meaningful triggers for DNS sync.

type CloudflareTunnelReconciler

type CloudflareTunnelReconciler struct {
	client.Client
	Scheme   *runtime.Scheme
	Recorder events.EventRecorder

	// APIReader provides uncached reads for watch mappers to avoid informer lag.
	APIReader client.Reader

	// CFClient is the Cloudflare API client. Injected for testing.
	CFClient cloudflare.Client

	// Builder creates Kubernetes resources for cloudflared.
	Builder cloudflared.Builder

	// CredentialCache caches validated Cloudflare clients to avoid repeated validations.
	CredentialCache *cloudflare.CredentialCache
}

CloudflareTunnelReconciler reconciles a CloudflareTunnel object. It manages the complete tunnel lifecycle: credential validation, tunnel creation/adoption, cloudflared deployment, and configuration sync.

func (*CloudflareTunnelReconciler) Reconcile

Reconcile handles the reconciliation loop for CloudflareTunnel resources. It ensures the Cloudflare tunnel exists, deploys cloudflared, and syncs configuration.

The reconciliation proceeds through these phases:

  1. Fetch the CloudflareTunnel resource
  2. Handle deletion via finalizers (cleanup tunnel from Cloudflare)
  3. Validate Cloudflare API credentials
  4. Ensure tunnel exists in Cloudflare (create or adopt)
  5. Deploy cloudflared connector (Deployment + Secret)
  6. Sync ingress configuration from Gateway/HTTPRoute resources
  7. Update status conditions

On error, the controller requeues after 30 seconds. On success, it requeues after 5 minutes for periodic configuration sync.

func (*CloudflareTunnelReconciler) SetupWithManager

func (r *CloudflareTunnelReconciler) SetupWithManager(mgr ctrl.Manager) error

SetupWithManager sets up the controller with the Manager. It configures watches for CloudflareTunnel and owned resources.

Watched resources:

  • CloudflareTunnel (primary resource)
  • Deployment (owned, for cloudflared)
  • Secret (owned, for tunnel token)
  • Gateway (via annotation cfgate.io/tunnel-ref)
  • HTTPRoute (via parent Gateway reference)

Gateway and HTTPRoute watches use GenerationChangedPredicate to prevent reconciliation loops from status-only updates.

type GatewayClassReconciler

type GatewayClassReconciler struct {
	client.Client
	Scheme *runtime.Scheme
}

GatewayClassReconciler reconciles GatewayClass resources to set Accepted status.

Per Gateway API spec (GEP-1364), controllers MUST set the Accepted condition on GatewayClass resources whose spec.controllerName matches. This reconciler sets Accepted=True for GatewayClasses managed by cfgate, enabling tools like Kiali and kubectl to show the class as ready. Non-matching GatewayClasses are ignored.

func (*GatewayClassReconciler) Reconcile

func (r *GatewayClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

Reconcile handles the reconciliation loop for GatewayClass resources. It sets Accepted=True on GatewayClasses with matching controllerName.

The reconciliation proceeds through these phases:

  1. Fetch the GatewayClass resource
  2. Check if spec.controllerName matches GatewayControllerName
  3. If match: set Accepted=True condition (only if not already set)
  4. Update status subresource

Non-matching GatewayClasses are ignored (another controller owns them). Periodic requeue (5m) provides self-healing: if a status update is lost due to conflict or transient error, the controller will re-verify and restore the Accepted condition on the next cycle.

func (*GatewayClassReconciler) SetupWithManager

func (r *GatewayClassReconciler) SetupWithManager(mgr ctrl.Manager) error

SetupWithManager sets up the GatewayClass controller with the Manager.

Watched resources:

  • GatewayClass (primary resource, with GenerationChangedPredicate)

GatewayClass is cluster-scoped. This is a separate controller from GatewayReconciler because GatewayClass and Gateway have different scoping and reconciliation needs.

type GatewayReconciler

type GatewayReconciler struct {
	client.Client
	Scheme   *runtime.Scheme
	Recorder events.EventRecorder
}

GatewayReconciler reconciles Gateway resources that reference CloudflareTunnel.

It validates tunnel references via the cfgate.io/tunnel-ref annotation, updates Gateway status conditions and addresses based on tunnel state, and counts attached routes for listener status. This controller does NOT manage DNS (see CloudflareDNS CRD) or tunnel lifecycle (see CloudflareTunnel CRD).

func (*GatewayReconciler) Reconcile

func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

Reconcile handles the reconciliation loop for Gateway resources. It validates the tunnel reference and updates Gateway status.

The reconciliation proceeds through these phases:

  1. Fetch the Gateway resource
  2. Verify GatewayClass is managed by cfgate
  3. Validate cfgate.io/tunnel-ref annotation
  4. Resolve the referenced CloudflareTunnel
  5. Update Gateway status (addresses, conditions, listeners)

On error or missing tunnel, the controller requeues after 30 seconds. On success, it requeues after 5 minutes for periodic status sync.

func (*GatewayReconciler) SetupWithManager

func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error

SetupWithManager sets up the controller with the Manager.

Watched resources:

  • Gateway (primary resource, with GenerationChangedPredicate)
  • CloudflareTunnel (with TunnelIDChangedPredicate, triggers status update when tunnel becomes ready)
  • HTTPRoute (with GenerationChangedPredicate, triggers attachedRoutes recount on route changes)

The controller only processes Gateways whose GatewayClass specifies cfgate.io/cloudflare-tunnel-controller as the controller name. GenerationChangedPredicate prevents reconciliation on status-only updates, reducing spurious reconciliations (201 reconciles/4h observed without predicate).

type HTTPRouteReconciler

type HTTPRouteReconciler struct {
	client.Client
	Scheme   *runtime.Scheme
	Recorder events.EventRecorder
}

HTTPRouteReconciler reconciles HTTPRoute resources. It validates routes against Gateway configuration, resolves backend Services, checks annotation validity, and resolves CloudflareAccessPolicy references.

func (*HTTPRouteReconciler) Reconcile

func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

Reconcile handles the reconciliation loop for HTTPRoute resources. It validates the route against parent Gateways, validates annotations, resolves backend Services, and resolves CloudflareAccessPolicy references.

The reconciliation proceeds through these phases:

  1. Fetch the HTTPRoute resource
  2. Validate cfgate.io/* annotations (emit warnings for deprecated ones)
  3. Preserve other controllers' status.parents[] entries
  4. Filter and validate only cfgate-managed parentRefs
  5. Resolve backend Service references
  6. Resolve cfgate.io/access-policy reference (if present)
  7. Merge conditions and update route status
  8. Emit reconciled event

parents[] preservation: Per Gateway API spec, controllers MUST NOT modify entries with non-matching controllerName. This implementation preserves entries from other controllers (e.g., Istio) and only rebuilds cfgate's own entries. Non-cfgate parentRefs are skipped entirely.

On error, the controller requeues after 30 seconds. On success, it requeues after 5 minutes for periodic validation.

func (*HTTPRouteReconciler) SetupWithManager

func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error

SetupWithManager sets up the controller with the Manager.

Watched resources:

  • HTTPRoute (primary resource, with CfgateAnnotationOrGenerationPredicate)
  • Gateway (with CfgateAnnotationOrGenerationPredicate for cfgate.io/* annotation awareness)
  • Service (no predicate -- service changes are rare and important)
  • CloudflareAccessPolicy (with GenerationChangedPredicate to filter status-only updates)

CfgateAnnotationOrGenerationPredicate on For() prevents status-only update loops while still allowing annotation-only cfgate.io/* changes to trigger reconcile.

type HostnameConfig

type HostnameConfig struct {
	// Target overrides the resource-level resolved target for this hostname.
	// Empty means use the resource-level target passed to syncRecords.
	Target string
	// TTL is the DNS record TTL in seconds (0 means use default)
	TTL int32
	// Proxied indicates if Cloudflare proxy should be enabled (nil means use default)
	Proxied *bool
	// RecordType is the DNS record type (CNAME, A, AAAA); defaults to CNAME
	RecordType string
}

HostnameConfig holds the effective DNS configuration for a hostname after merging route-discovered and explicit configuration sources.

Directories

Path Synopsis
Package annotations provides annotation parsing utilities for cfgate controllers.
Package annotations provides annotation parsing utilities for cfgate controllers.
Package context provides wrapper types for clean separation between raw API types and processing logic.
Package context provides wrapper types for clean separation between raw API types and processing logic.
Package features provides CRD detection and feature flags for optional Gateway API resources, enabling graceful degradation when supporting resources are unavailable.
Package features provides CRD detection and feature flags for optional Gateway API resources, enabling graceful degradation when supporting resources are unavailable.
Package status provides condition management utilities for cfgate controllers.
Package status provides condition management utilities for cfgate controllers.

Jump to

Keyboard shortcuts

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