bindy/reconcilers/bind9cluster/
mod.rs1pub mod config;
20pub mod drift;
21pub mod instances;
22pub mod status_helpers;
23pub mod types;
24
25pub use instances::{create_managed_instance, delete_bind9cluster, delete_managed_instance};
27pub use status_helpers::calculate_cluster_status;
28
29use config::create_or_update_cluster_configmap;
31use drift::detect_instance_drift;
32use instances::reconcile_managed_instances;
33use status_helpers::update_status;
34#[allow(clippy::wildcard_imports)]
35use types::*;
36
37use crate::labels::FINALIZER_BIND9_CLUSTER;
38use crate::reconcilers::finalizers::{ensure_finalizer, handle_deletion, FinalizerCleanup};
39
40#[async_trait::async_trait]
42impl FinalizerCleanup for Bind9Cluster {
43 async fn cleanup(&self, client: &Client) -> Result<()> {
44 let namespace = self.namespace().unwrap_or_default();
45 let name = self.name_any();
46 instances::delete_cluster_instances(client, &namespace, &name).await
47 }
48}
49
50pub async fn reconcile_bind9cluster(ctx: Arc<Context>, cluster: Bind9Cluster) -> Result<()> {
74 let client = ctx.client.clone();
75 let namespace = cluster.namespace().unwrap_or_default();
76 let name = cluster.name_any();
77
78 info!("Reconciling Bind9Cluster: {}/{}", namespace, name);
79 debug!(
80 namespace = %namespace,
81 name = %name,
82 generation = ?cluster.metadata.generation,
83 "Starting Bind9Cluster reconciliation"
84 );
85
86 if cluster.metadata.deletion_timestamp.is_some() {
88 return handle_deletion(&client, &cluster, FINALIZER_BIND9_CLUSTER).await;
89 }
90
91 ensure_finalizer(&client, &cluster, FINALIZER_BIND9_CLUSTER).await?;
93
94 let current_generation = cluster.metadata.generation;
96 let observed_generation = cluster.status.as_ref().and_then(|s| s.observed_generation);
97
98 let spec_changed =
100 crate::reconcilers::should_reconcile(current_generation, observed_generation);
101
102 let drift_detected = if spec_changed {
104 false
105 } else {
106 detect_instance_drift(&client, &cluster, &namespace, &name).await?
107 };
108
109 if spec_changed || drift_detected {
110 if drift_detected {
111 info!(
112 "Spec unchanged but instance drift detected for cluster {}/{}",
113 namespace, name
114 );
115 } else {
116 debug!(
117 "Reconciliation needed: current_generation={:?}, observed_generation={:?}",
118 current_generation, observed_generation
119 );
120 }
121
122 create_or_update_cluster_configmap(&client, &cluster).await?;
124
125 reconcile_managed_instances(&ctx, &cluster).await?;
127 } else {
128 debug!(
129 "Spec unchanged (generation={:?}) and no drift detected, skipping resource reconciliation",
130 current_generation
131 );
132 }
133
134 let instances: Vec<Bind9Instance> =
137 list_cluster_instances(&client, &cluster, &namespace, &name).await?;
138
139 let (instance_count, ready_instances, instance_names, conditions) =
141 calculate_cluster_status(&instances, &namespace, &name);
142
143 update_status(
145 &client,
146 &cluster,
147 conditions,
148 instance_count,
149 ready_instances,
150 instance_names,
151 )
152 .await?;
153
154 Ok(())
155}
156
157async fn list_cluster_instances(
178 client: &Client,
179 cluster: &Bind9Cluster,
180 namespace: &str,
181 name: &str,
182) -> Result<Vec<Bind9Instance>> {
183 let instances_api: Api<Bind9Instance> = Api::namespaced(client.clone(), namespace);
185 let list_params = ListParams::default();
186 debug!(namespace = %namespace, "Listing Bind9Instance resources");
187
188 match instances_api.list(&list_params).await {
189 Ok(list) => {
190 debug!(
191 total_instances_in_ns = list.items.len(),
192 "Listed Bind9Instance resources"
193 );
194 let filtered: Vec<_> = list
196 .items
197 .into_iter()
198 .filter(|instance| instance.spec.cluster_ref == name)
199 .collect();
200 debug!(
201 filtered_instances = filtered.len(),
202 cluster_ref = %name,
203 "Filtered instances by cluster reference"
204 );
205 Ok(filtered)
206 }
207 Err(e) => {
208 error!(
209 "Failed to list Bind9Instance resources for cluster {}/{}: {}",
210 namespace, name, e
211 );
212
213 let error_condition = Condition {
215 r#type: CONDITION_TYPE_READY.to_string(),
216 status: "False".to_string(),
217 reason: Some(REASON_NOT_READY.to_string()),
218 message: Some(format!("Failed to list instances: {e}")),
219 last_transition_time: Some(Utc::now().to_rfc3339()),
220 };
221 update_status(client, cluster, vec![error_condition], 0, 0, vec![]).await?;
222
223 Err(e.into())
224 }
225 }
226}