controller

package
v0.7.1 Latest Latest
Warning

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

Go to latest
Published: May 1, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Index

Constants

View Source
const (
	AnnotationTarget             = "cloudflare.io/target"
	AnnotationZoneRef            = "cloudflare.io/zone-ref"
	AnnotationZoneRefNamespace   = "cloudflare.io/zone-ref-namespace"
	AnnotationTunnelRefNamespace = "cloudflare.io/tunnel-ref-namespace"
	AnnotationTunnelUpstream     = "cloudflare.io/tunnel-upstream"
	AnnotationHostnames          = "cloudflare.io/hostnames"
	AnnotationPort               = "cloudflare.io/port"
	AnnotationScheme             = "cloudflare.io/scheme"
	AnnotationProxied            = "cloudflare.io/proxied"
	AnnotationTTL                = "cloudflare.io/ttl"
	AnnotationAdopt              = "cloudflare.io/adopt"

	AnnotationPrefix = "cloudflare.io/"

	// AnnotationValueTrue is the canonical "true" value for boolean annotation flags
	// such as cloudflare.io/adopt.
	AnnotationValueTrue = "true"

	// AnnotationRegistryFor marks an emitted TXT-registry CloudflareDNSRecord
	// so the DNS controller does not re-emit a TXT for it.
	AnnotationRegistryFor = "cloudflare.io/registry-for"
	// AnnotationConfigHash goes on the connector Deployment's pod template
	// and the rendered ConfigMap so config changes trigger rollouts.
	AnnotationConfigHash = "cloudflare.io/config-hash"
)

Cloudflare-operator annotation keys (v1).

These form the public, stable annotation vocabulary that source controllers (httproute_source, service_source) consume. Once shipped, semantics do not change in breaking ways — new features get new keys.

View Source
const (
	LabelSourceKind      = "cloudflare.io/source-kind"
	LabelSourceNamespace = "cloudflare.io/source-namespace"
	LabelSourceName      = "cloudflare.io/source-name"
	LabelManagedBy       = "cloudflare.io/managed-by"
)

Labels applied to emitted CloudflareDNSRecord / CloudflareTunnelRule CRs for source-based discovery via label selectors.

View Source
const DefaultConnectorImage = defaultConnectorRepo + ":" + defaultConnectorTag

DefaultConnectorImage is used when spec.connector.image is unset.

Variables

View Source
var (
	// ErrForeignTXTOwner indicates a companion TXT record exists whose owner
	// field does not match TxtOwnerID. The operator refuses to overwrite.
	ErrForeignTXTOwner = errors.New("record exists with foreign TXT owner")

	// ErrTXTRegistryGap indicates a DNS record exists at the target name+type
	// but has no companion ownership TXT, and the CR has not opted in to adopt.
	ErrTXTRegistryGap = errors.New("record exists without ownership TXT and source is not opted in to adopt")

	// ErrSourceLabelsMissing is returned by writeRegistryTXT when the managed
	// CloudflareDNSRecord is missing the cloudflare.io/source-* labels needed
	// to build the registry payload.
	ErrSourceLabelsMissing = errors.New("managed CR is missing cloudflare.io/source-* labels")
)

Sentinel errors returned for classifiable failure verdicts. Callers MUST use errors.Is for comparison — never compare error strings.

View Source
var ErrAmbiguousZone = errors.New("ambiguous CloudflareZone for hostname")

ErrAmbiguousZone is returned by ResolveZoneForHostname when two or more CloudflareZones share an identical spec.Name matching the hostname. This is a configuration error, not a transient one.

View Source
var ErrInvalidTarget = errors.New("invalid cloudflare.io/target value")

ErrInvalidTarget is returned by ParseTarget on malformed input.

View Source
var ErrNoGatewayAddress = errors.New("no Gateway address populated in status")

ErrNoGatewayAddress is returned when a target=address reconcile cannot proceed because the parent Gateway has no status addresses populated yet.

View Source
var ErrNoMatchingZone = errors.New("no CloudflareZone matches hostname")

ErrNoMatchingZone is returned by ResolveZoneForHostname when no CloudflareZone's spec.name is a suffix of the hostname.

View Source
var ErrNoServicePorts = errors.New("service has no ports")

ErrNoServicePorts is returned by resolveServiceBackend when the Service has no ports defined.

View Source
var ErrPortNotFound = errors.New("port not found on Service")

ErrPortNotFound is returned by resolveServiceBackend when the requested port name or number is not present on the Service.

View Source
var ErrUnownedDeployment = stderrors.New("refusing to adopt Deployment not owned by this tunnel")

ErrUnownedDeployment is returned when reconcileConnectorResources finds an existing Deployment that is not owned by the reconciled CloudflareTunnel. Tests MUST use errors.Is(err, ErrUnownedDeployment) to assert this case.

View Source
var ErrZoneRefNotReady = errors.New("zone reference is not ready")

ErrZoneRefNotReady is returned by ResolveZoneID when the referenced CloudflareZone exists but has not yet populated its status.ZoneID. Callers can check with errors.Is to log this as an Info-level "waiting on dependency" rather than an Error, since it resolves on its own once the zone reconciles.

Functions

func BuildConnectorConfigMap added in v0.6.0

func BuildConnectorConfigMap(tun *cloudflarev1alpha1.CloudflareTunnel, renderedConfig []byte, configHash string) *corev1.ConfigMap

BuildConnectorConfigMap produces the desired ConfigMap carrying the rendered cloudflared config.yaml.

func BuildConnectorDeployment added in v0.6.0

func BuildConnectorDeployment(tun *cloudflarev1alpha1.CloudflareTunnel, configHash string) *appsv1.Deployment

BuildConnectorDeployment produces the desired Deployment running cloudflared.

Replicas: when cspec is nil (defensive path), default to 2. When cspec is non-nil, use cspec.Replicas directly — this preserves a user-set value of 0 (scale-down intent). The apiserver default of 2 fires only on field-absent creates, so by the time the controller reads the spec the value is always meaningful.

Image: see resolveConnectorImage for the four-case partial-override matrix.

Resources: cspec.Resources.Requests and cspec.Resources.Limits are defaulted independently so a user supplying only Requests still gets the Memory limit safety floor (and vice versa). Defaults: 10m CPU + 128Mi Memory requests, 256Mi Memory limit.

Note: cspec.Resources is shallow-copied into the container; ResourceList and other map fields share storage with the input. Build* functions never mutate the spec in place, so this aliasing is safe.

func BuildConnectorServiceAccount added in v0.6.0

func BuildConnectorServiceAccount(tun *cloudflarev1alpha1.CloudflareTunnel) *corev1.ServiceAccount

BuildConnectorServiceAccount produces the desired ServiceAccount for tun.

func ListZonesClusterWide added in v0.6.0

func ListZonesClusterWide(ctx context.Context, c client.Client) ([]cloudflarev1alpha1.CloudflareZone, error)

ListZonesClusterWide returns all CloudflareZone CRs in the cluster.

func MergeCloudflareAnnotations added in v0.6.0

func MergeCloudflareAnnotations(parent, child map[string]string) map[string]string

MergeCloudflareAnnotations merges parent (e.g. Gateway) and child (e.g. Route) annotations, keeping only the cloudflare.io/* subset. Child values override parent values on the same key.

func ReconcileConnectorAndRules added in v0.6.0

ReconcileConnectorAndRules performs the Task 8 additions to the tunnel reconcile: aggregates CloudflareTunnelRule CRs for this tunnel, renders config.yaml, reconciles the connector workload (when enabled), and writes per-rule + per-tunnel status.

Pure controller-runtime operation: no Cloudflare API calls. Called by Reconcile after tunnel provisioning has populated TunnelID/TunnelCNAME. preStatus is the status snapshot taken before Reconcile began; it is used by writeTunnelAggStatus to set LastSyncedAt only when status actually changed.

func ResolveZoneForHostname added in v0.6.0

func ResolveZoneForHostname(hostname string, zones []cloudflarev1alpha1.CloudflareZone) (*cloudflarev1alpha1.CloudflareZone, error)

ResolveZoneForHostname picks the CloudflareZone with the longest spec.name suffix match against hostname. If two zones are equally-specific, returns an error (expected in practice only when zones are literally identical; CR names are unique by namespace so this is rare but guarded).

func ResolveZoneID

func ResolveZoneID(ctx context.Context, k8sClient client.Client, obj zoneReferencer) (string, error)

ResolveZoneID returns the Cloudflare zone ID for obj: either its inline Spec.ZoneID, or the ZoneID from the CloudflareZone it references via Spec.ZoneRef (looked up in obj's namespace). Returns ErrZoneRefNotReady (wrapped) when the referenced zone exists but hasn't populated ZoneID yet.

Types

type AggregationResult added in v0.6.0

type AggregationResult struct {
	// Rendered is the cloudflared config.yaml content.
	Rendered []byte
	// ConfigHash is sha256 of Rendered, hex-encoded.
	ConfigHash string
	// Decisions maps ruleKey -> per-rule verdict.
	Decisions map[types.NamespacedName]RuleDecision
}

AggregationResult is the output of Aggregate.

func Aggregate added in v0.6.0

Aggregate produces the rendered config.yaml content and per-rule verdicts for a set of CloudflareTunnelRule CRs targeting the same tunnel, plus the tunnel's spec.routing defaults.

Ordering rules (spec §4.3):

  1. Rules sorted by spec.priority desc, then metadata.name asc.
  2. Duplicate hostnames resolved by first writer (creationTimestamp asc, then UID asc); losers get RuleDuplicateHostname.
  3. config.yaml order: included rules, then spec.routing.defaultBackend (if set), then a fixed final http_status:404 catch-all.

type CloudflareDNSRecordReconciler

type CloudflareDNSRecordReconciler struct {
	client.Client
	Scheme        *runtime.Scheme
	Recorder      record.EventRecorder
	ClientFactory *cfclient.ClientFactory
	IPResolver    *ipresolver.Resolver
	DNSClientFn   func(apiToken string) cfclient.DNSClient
	// Registry holds optional TXT-registry configuration. The zero value
	// (TxtOwnerID == "") disables all registry behaviour.
	Registry RegistryConfig
}

CloudflareDNSRecordReconciler reconciles a CloudflareDNSRecord object

func (*CloudflareDNSRecordReconciler) Reconcile

Reconcile moves the current state of the cluster closer to the desired state for a CloudflareDNSRecord resource. It handles the full lifecycle of DNS records including creation, updates, adoption of existing records, and deletion.

func (*CloudflareDNSRecordReconciler) SetupWithManager

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

SetupWithManager sets up the controller with the Manager.

type CloudflareRulesetReconciler

type CloudflareRulesetReconciler struct {
	client.Client
	Scheme          *runtime.Scheme
	Recorder        record.EventRecorder
	ClientFactory   *cfclient.ClientFactory
	RulesetClientFn func(apiToken string) cfclient.RulesetClient
}

CloudflareRulesetReconciler reconciles a CloudflareRuleset object

func (*CloudflareRulesetReconciler) Reconcile

Reconcile moves the current state of the cluster closer to the desired state for a CloudflareRuleset resource. It handles the full lifecycle of rulesets including creation, updates, adoption of existing rulesets, and deletion.

func (*CloudflareRulesetReconciler) SetupWithManager

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

SetupWithManager sets up the controller with the Manager.

type CloudflareTunnelReconciler

type CloudflareTunnelReconciler struct {
	client.Client
	Scheme         *runtime.Scheme
	Recorder       record.EventRecorder
	ClientFactory  *cfclient.ClientFactory
	TunnelClientFn func(apiToken string) cfclient.TunnelClient
}

CloudflareTunnelReconciler reconciles a CloudflareTunnel object

func (*CloudflareTunnelReconciler) Reconcile

Reconcile moves the current state of the cluster closer to the desired state for a CloudflareTunnel resource. It handles the full lifecycle of tunnels including creation, adoption of existing tunnels, credential Secret generation, and deletion.

func (*CloudflareTunnelReconciler) SetupWithManager

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

SetupWithManager sets up the controller with the Manager.

type CloudflareZoneConfigReconciler

type CloudflareZoneConfigReconciler struct {
	client.Client
	Scheme        *runtime.Scheme
	Recorder      record.EventRecorder
	ClientFactory *cfclient.ClientFactory
	ZoneClientFn  func(apiToken string) cfclient.ZoneClient
}

CloudflareZoneConfigReconciler reconciles a CloudflareZoneConfig object

func (*CloudflareZoneConfigReconciler) Reconcile

Reconcile moves the current state of the cluster closer to the desired state for a CloudflareZoneConfig resource. It handles applying zone settings to Cloudflare and tracking the applied settings count.

func (*CloudflareZoneConfigReconciler) SetupWithManager

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

SetupWithManager sets up the controller with the Manager.

type CloudflareZoneReconciler

type CloudflareZoneReconciler struct {
	client.Client
	Scheme                *runtime.Scheme
	Recorder              record.EventRecorder
	ClientFactory         *cfclient.ClientFactory
	ZoneLifecycleClientFn func(apiToken string) cfclient.ZoneLifecycleClient
}

CloudflareZoneReconciler reconciles a CloudflareZone object

func (*CloudflareZoneReconciler) Reconcile

func (*CloudflareZoneReconciler) SetupWithManager

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

SetupWithManager sets up the controller with the Manager.

type ConnectorResourceNames added in v0.6.0

type ConnectorResourceNames struct {
	Deployment     string
	ConfigMap      string
	ServiceAccount string
}

ConnectorResourceNames are the deterministic names of the resources the connector sub-reconciler manages for a given CloudflareTunnel.

func ConnectorNames added in v0.6.0

ConnectorNames returns the deterministic resource names for tun.

type HTTPRouteSourceReconciler added in v0.6.0

type HTTPRouteSourceReconciler struct {
	client.Client
	Recorder    record.EventRecorder
	TxtOwnerID  string
	AffixConfig cfclient.AffixConfig
}

HTTPRouteSourceReconciler watches HTTPRoutes that carry cloudflare.io/* annotations (either directly or inherited from a parent Gateway) and emits CloudflareDNSRecord + CloudflareTunnelRule CRs.

func (*HTTPRouteSourceReconciler) Reconcile added in v0.6.0

Reconcile processes a single HTTPRoute reconcile request.

func (*HTTPRouteSourceReconciler) SetupWithManager added in v0.6.0

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

SetupWithManager registers the HTTPRouteSourceReconciler with the manager.

type RegistryAction added in v0.6.0

type RegistryAction string

RegistryAction is the verdict returned by RegistryDecide. Using string constants gives self-documenting log/event output without a separate String() method.

const (
	// RegistryActionCreate: no existing DNS record found — proceed to create.
	RegistryActionCreate RegistryAction = "Create"
	// RegistryActionReconcile: existing record is owned by us — reconcile content drift.
	RegistryActionReconcile RegistryAction = "Reconcile"
	// RegistryActionAdopt: existing TXT is owned by a listed import-owner — rewrite TXT and claim.
	RegistryActionAdopt RegistryAction = "Adopt"
	// RegistryActionAdoptOrphan: existing DNS record has no TXT and cloudflare.io/adopt is set.
	RegistryActionAdoptOrphan RegistryAction = "AdoptOrphan"
	// RegistryActionRefuseForeignOwner: existing TXT is owned by someone else — do not touch.
	RegistryActionRefuseForeignOwner RegistryAction = "RefuseForeignOwner"
	// RegistryActionRefuseNoTXT: existing record has no TXT and adopt is not opted in.
	RegistryActionRefuseNoTXT RegistryAction = "RefuseNoTXT"
)

func RegistryDecide added in v0.6.0

func RegistryDecide(
	cfg RegistryConfig,
	existing *cfclient.DNSRecord,
	existingTXTContent string,
	adoptOptIn bool,
) RegistryAction

RegistryDecide applies the §11.3 decision table and returns the action the controller should take for the current reconcile pass.

Parameters:

  • cfg: full RegistryConfig (used for TxtOwnerID, TxtImportOwners, TxtImportDecryptKeys)
  • existing: the current DNS record at name+type, nil if absent
  • existingTXTContent: raw wire content from the companion TXT record, "" if absent
  • adoptOptIn: true when the CloudflareDNSRecord carries cloudflare.io/adopt="true"

DecryptPayload errors and DecodeRegistryPayload errors both route to RegistryActionRefuseForeignOwner. This conservatively conflates "foreign-controller wrote an encrypted blob with a key we don't hold" with "TXT corruption" — both cases require human intervention and should not block adoption candidates.

type RegistryConfig added in v0.6.0

type RegistryConfig struct {
	// TxtOwnerID is the ownership token written into companion TXT records.
	// Required to enable registry behaviour. When empty, all registry checks
	// are skipped and the controller behaves identically to pre-registry releases.
	TxtOwnerID string

	// TxtImportOwners enumerates prior controllers (e.g. "external-dns") whose
	// TXT records this operator may adopt. A TXT whose payload.Owner appears in
	// this list, and whose payload is otherwise valid, results in
	// RegistryActionAdopt — this controller rewrites the TXT to claim the record.
	TxtImportOwners []string // restore — plan §11.3

	// AffixConfig controls companion-TXT name derivation.
	// The zero value matches external-dns's default affix scheme.
	AffixConfig cfclient.AffixConfig

	// TxtEncryptAESKey is an optional 32-byte AES-256 key.  When non-nil,
	// companion TXT payloads are AES-CBC encrypted before writing.
	// When nil (the default), payloads are written as plaintext.
	TxtEncryptAESKey []byte

	// TxtImportDecryptKeys is a list of AES-256 keys tried in order when
	// decoding an existing companion TXT. Supports key rotation: include the
	// old key here and the new key in TxtEncryptAESKey.
	// When empty/nil, only plaintext incoming TXTs can be read. Encrypted TXT
	// records will fail to decrypt and route to RefuseForeignOwner.
	TxtImportDecryptKeys [][]byte
}

RegistryConfig holds TXT-registry configuration for the DNS record controller. The zero value is valid and disables registry behaviour entirely (TxtOwnerID == "" short-circuits all registry logic).

type RuleDecision added in v0.6.0

type RuleDecision struct {
	Status          RuleDecisionStatus
	Message         string
	ResolvedBackend string // URL actually written to config.yaml; "" if not included
}

RuleDecision records why a rule was or was not included.

type RuleDecisionStatus added in v0.6.0

type RuleDecisionStatus string

RuleDecisionStatus is the per-rule verdict from an aggregation pass.

const (
	// RuleIncluded means the rule is present in the rendered config.yaml.
	RuleIncluded RuleDecisionStatus = "Included"
	// RuleDuplicateHostname means the rule was excluded because a
	// higher-precedence rule already claimed one of its hostnames.
	RuleDuplicateHostname RuleDecisionStatus = "DuplicateHostname"
	// RuleInvalid means the rule's spec did not pass semantic validation
	// (exactly-one-backend rule, non-empty hostnames).
	RuleInvalid RuleDecisionStatus = "Invalid"
)

type ServiceSourceReconciler added in v0.6.0

type ServiceSourceReconciler struct {
	client.Client
	Recorder    record.EventRecorder
	TxtOwnerID  string
	AffixConfig cfclient.AffixConfig
}

ServiceSourceReconciler watches Services that carry cloudflare.io/* annotations and emits CloudflareDNSRecord + CloudflareTunnelRule CRs.

func (*ServiceSourceReconciler) Reconcile added in v0.6.0

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

Reconcile processes a single Service reconcile request.

func (*ServiceSourceReconciler) SetupWithManager added in v0.6.0

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

SetupWithManager registers the ServiceSourceReconciler with the manager.

type TargetKind added in v0.6.0

type TargetKind string

TargetKind is the kind field of a parsed cloudflare.io/target value.

const (
	TargetKindTunnel  TargetKind = "tunnel"
	TargetKindCNAME   TargetKind = "cname"
	TargetKindAddress TargetKind = "address"
)

type TargetSpec added in v0.6.0

type TargetSpec struct {
	Kind  TargetKind
	Name  string // populated when Kind=Tunnel
	CNAME string // populated when Kind=CNAME
}

TargetSpec is a parsed cloudflare.io/target annotation value.

tunnel:<name>    -> Kind=Tunnel,  Name=<name>
cname:<fqdn>     -> Kind=CNAME,   CNAME=<fqdn>
address          -> Kind=Address

func ParseTarget added in v0.6.0

func ParseTarget(raw string) (TargetSpec, error)

ParseTarget parses a cloudflare.io/target value. Kinds are matched case-sensitively ("tunnel", "cname", "address") — callers MUST use the lowercase canonical form.

Jump to

Keyboard shortcuts

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