bindy/reconcilers/mod.rs
1// Copyright (c) 2025 Erick Bourgeois, firestoned
2// SPDX-License-Identifier: MIT
3
4//! Kubernetes reconciliation controllers for DNS resources.
5//!
6//! This module contains the reconciliation logic for all Bindy Custom Resources.
7//! Each reconciler watches for changes to its respective resource type and updates
8//! BIND9 configurations accordingly.
9//!
10//! # Reconciliation Architecture
11//!
12//! Bindy follows the standard Kubernetes controller pattern:
13//!
14//! 1. **Watch** - Monitor resource changes via Kubernetes API
15//! 2. **Reconcile** - Compare desired state (CRD spec) with actual state
16//! 3. **Update** - Modify BIND9 configuration to match desired state
17//! 4. **Status** - Report reconciliation results back to Kubernetes
18//!
19//! # Available Reconcilers
20//!
21//! ## Infrastructure
22//!
23//! - [`reconcile_bind9cluster`] - Manages namespace-scoped BIND9 cluster status
24//! - [`delete_bind9cluster`] - Cleans up namespace-scoped BIND9 cluster resources
25//! - [`reconcile_clusterbind9provider`] - Manages cluster-scoped BIND9 provider status
26//! - [`delete_clusterbind9provider`] - Cleans up cluster-scoped BIND9 provider resources
27//! - [`reconcile_bind9instance`] - Creates/updates BIND9 server deployments
28//! - [`delete_bind9instance`] - Cleans up BIND9 server resources
29//!
30//! ## DNS Zones
31//!
32//! - [`reconcile_dnszone`] - Creates/updates DNS zone files
33//! - [`delete_dnszone`] - Removes DNS zones
34//!
35//! ## DNS Records
36//!
37//! - [`reconcile_a_record`] - Manages IPv4 address records
38//! - [`reconcile_aaaa_record`] - Manages IPv6 address records
39//! - [`reconcile_cname_record`] - Manages canonical name aliases
40//! - [`reconcile_mx_record`] - Manages mail exchange records
41//! - [`reconcile_txt_record`] - Manages text records
42//! - [`reconcile_ns_record`] - Manages nameserver delegation
43//! - [`reconcile_srv_record`] - Manages service location records
44//! - [`reconcile_caa_record`] - Manages certificate authority authorization
45//!
46//! # Example: Using a Reconciler
47//!
48//! ```rust,no_run
49//! use bindy::reconcilers::reconcile_dnszone;
50//! use bindy::crd::DNSZone;
51//! use bindy::bind9::Bind9Manager;
52//! use bindy::context::Context;
53//! use std::sync::Arc;
54//!
55//! async fn reconcile_zone(ctx: Arc<Context>, dnszone: DNSZone) -> anyhow::Result<()> {
56//! let zone_manager = Bind9Manager::new();
57//!
58//! reconcile_dnszone(ctx, dnszone, &zone_manager).await?;
59//! Ok(())
60//! }
61//! ```
62
63pub mod bind9cluster;
64pub mod bind9instance;
65pub mod clusterbind9provider;
66pub mod dnszone;
67pub mod finalizers;
68pub mod pagination;
69pub mod records;
70pub mod resources;
71pub mod retry;
72pub mod status;
73
74#[cfg(test)]
75mod clusterbind9provider_tests;
76#[cfg(test)]
77mod records_tests;
78#[cfg(test)]
79mod status_tests;
80
81pub use bind9cluster::{delete_bind9cluster, reconcile_bind9cluster};
82pub use bind9instance::{delete_bind9instance, reconcile_bind9instance, reconcile_instance_zones};
83pub use clusterbind9provider::{delete_clusterbind9provider, reconcile_clusterbind9provider};
84pub use dnszone::{delete_dnszone, discovery::find_zones_selecting_record, reconcile_dnszone};
85pub use records::{
86 delete_record, reconcile_a_record, reconcile_aaaa_record, reconcile_caa_record,
87 reconcile_cname_record, reconcile_mx_record, reconcile_ns_record, reconcile_srv_record,
88 reconcile_txt_record,
89};
90
91/// Check if a resource's spec has changed by comparing generation with `observed_generation`.
92///
93/// This is the standard Kubernetes pattern for determining if reconciliation is needed.
94/// The `metadata.generation` field is incremented by Kubernetes only when the spec changes,
95/// while `status.observed_generation` is set by the controller after processing a spec.
96///
97/// # Arguments
98///
99/// * `current_generation` - The resource's current `metadata.generation`
100/// * `observed_generation` - The controller's last `status.observed_generation`
101///
102/// # Returns
103///
104/// * `true` - Reconciliation is needed (spec changed or first reconciliation)
105/// * `false` - No reconciliation needed (spec unchanged, status-only update)
106///
107/// # Example
108///
109/// ```rust,ignore
110/// use bindy::reconcilers::should_reconcile;
111///
112/// fn check_if_reconcile_needed(resource: &MyResource) -> bool {
113/// let current = resource.metadata.generation;
114/// let observed = resource.status.as_ref()
115/// .and_then(|s| s.observed_generation);
116///
117/// should_reconcile(current, observed)
118/// }
119/// ```
120///
121/// # Kubernetes Generation Semantics
122///
123/// - **`metadata.generation`**: Incremented by Kubernetes API server when spec changes
124/// - **`status.observed_generation`**: Set by controller to match `metadata.generation` after reconciliation
125/// - When they match: spec hasn't changed since last reconciliation → skip work
126/// - When they differ: spec has changed → reconcile
127/// - When `observed_generation` is None: first reconciliation → reconcile
128#[must_use]
129pub fn should_reconcile(current_generation: Option<i64>, observed_generation: Option<i64>) -> bool {
130 match (current_generation, observed_generation) {
131 (Some(current), Some(observed)) => current != observed,
132 (Some(_), None) => true, // First reconciliation
133 _ => false, // No generation tracking available
134 }
135}
136
137/// Check if a status value has actually changed compared to the current status.
138///
139/// This helper prevents unnecessary status updates that would trigger reconciliation loops.
140/// It compares a new status value with the existing status and returns `true` only if
141/// they differ, indicating an update is needed.
142///
143/// # Arguments
144///
145/// * `current_value` - The current status value (from existing resource)
146/// * `new_value` - The new status value to potentially set
147///
148/// # Returns
149///
150/// * `true` - Status has changed and needs updating
151/// * `false` - Status is unchanged, skip the update
152///
153/// # Example
154///
155/// ```rust,ignore
156/// use bindy::reconcilers::status_changed;
157///
158/// let current_ready = instance.status.as_ref()
159/// .and_then(|s| s.ready_replicas);
160/// let new_ready = Some(3);
161///
162/// if status_changed(¤t_ready, &new_ready) {
163/// // Status has changed, safe to update
164/// update_status(client, instance, new_ready).await?;
165/// }
166/// ```
167///
168/// # Why This Matters
169///
170/// In kube-rs, status updates trigger "object updated" events which cause new reconciliations.
171/// Without this check, updating status on every reconciliation creates a tight loop:
172///
173/// 1. Reconcile → Update status
174/// 2. Status update → "object updated" event
175/// 3. Event → New reconciliation
176/// 4. Repeat from step 1 (infinite loop)
177///
178/// By only updating when status actually changes, we break this cycle.
179#[must_use]
180pub fn status_changed<T: PartialEq>(current_value: &Option<T>, new_value: &Option<T>) -> bool {
181 current_value != new_value
182}
183
184#[cfg(test)]
185mod mod_tests;