bindy/reconcilers/dnszone/
status_helpers.rs

1// Copyright (c) 2025 Erick Bourgeois, firestoned
2// SPDX-License-Identifier: MIT
3
4//! Status calculation and finalization helpers for DNSZone reconciliation.
5//!
6//! This module contains functions for calculating expected instance counts
7//! and determining the final Ready/Degraded status of a DNSZone.
8
9use anyhow::Result;
10use kube::Client;
11
12use crate::crd::InstanceReference;
13
14/// Calculate expected instance counts (primary and secondary).
15///
16/// This function filters the instance references to determine how many
17/// primary and secondary instances should be configured.
18///
19/// # Arguments
20///
21/// * `client` - Kubernetes API client
22/// * `instance_refs` - List of instance references assigned to the zone
23///
24/// # Returns
25///
26/// Tuple of `(expected_primary_count, expected_secondary_count)`
27///
28/// # Errors
29///
30/// Returns an error if Kubernetes API calls fail
31pub async fn calculate_expected_instance_counts(
32    client: &Client,
33    instance_refs: &[InstanceReference],
34) -> Result<(usize, usize)> {
35    let expected_primary_count = super::primary::filter_primary_instances(client, instance_refs)
36        .await
37        .map(|refs| refs.len())
38        .unwrap_or(0);
39
40    let expected_secondary_count =
41        super::secondary::filter_secondary_instances(client, instance_refs)
42            .await
43            .map(|refs| refs.len())
44            .unwrap_or(0);
45
46    Ok((expected_primary_count, expected_secondary_count))
47}
48
49/// Determine final zone status and apply conditions.
50///
51/// This function calculates the final Ready or Degraded status based on:
52/// - Whether any degraded conditions were set during reconciliation
53/// - Whether all expected instances were successfully configured
54/// - Number of records discovered
55///
56/// The function then applies all accumulated status changes to the API server
57/// in a single atomic operation.
58///
59/// # Arguments
60///
61/// * `status_updater` - Status updater with accumulated changes
62/// * `client` - Kubernetes API client
63/// * `zone_name` - DNS zone name (e.g., "example.com")
64/// * `namespace` - Kubernetes namespace of the DNSZone resource
65/// * `name` - Name of the DNSZone resource
66/// * `primary_count` - Number of primary instances successfully configured
67/// * `secondary_count` - Number of secondary instances successfully configured
68/// * `expected_primary_count` - Expected number of primary instances
69/// * `expected_secondary_count` - Expected number of secondary instances
70/// * `records_count` - Number of DNS records discovered
71/// * `generation` - Metadata generation to set as observed
72///
73/// # Errors
74///
75/// Returns an error if status update fails to apply
76#[allow(clippy::too_many_arguments)]
77pub async fn finalize_zone_status(
78    status_updater: &mut crate::reconcilers::status::DNSZoneStatusUpdater,
79    client: &Client,
80    zone_name: &str,
81    namespace: &str,
82    name: &str,
83    primary_count: usize,
84    secondary_count: usize,
85    expected_primary_count: usize,
86    expected_secondary_count: usize,
87    records_count: usize,
88    generation: Option<i64>,
89) -> Result<()> {
90    // Set observed generation
91    status_updater.set_observed_generation(generation);
92
93    // Set final Ready/Degraded status based on reconciliation outcome
94    // Only set Ready=True if there were NO degraded conditions during reconciliation
95    // AND all expected instances were successfully configured
96    if status_updater.has_degraded_condition() {
97        // Keep the Degraded condition that was already set, don't overwrite with Ready
98        tracing::info!(
99            "DNSZone {}/{} reconciliation completed with degraded state - will retry faster",
100            namespace,
101            name
102        );
103    } else if primary_count < expected_primary_count || secondary_count < expected_secondary_count {
104        // Not all instances were configured - set Degraded condition
105        status_updater.set_condition(
106            "Degraded",
107            "True",
108            "PartialReconciliation",
109            &format!(
110                "Zone {} configured on {}/{} primary and {}/{} secondary instance(s) - {} instance(s) pending",
111                zone_name,
112                primary_count,
113                expected_primary_count,
114                secondary_count,
115                expected_secondary_count,
116                (expected_primary_count - primary_count)
117                    + (expected_secondary_count - secondary_count)
118            ),
119        );
120        tracing::info!(
121            "DNSZone {}/{} partially configured: {}/{} primaries, {}/{} secondaries",
122            namespace,
123            name,
124            primary_count,
125            expected_primary_count,
126            secondary_count,
127            expected_secondary_count
128        );
129    } else {
130        // All reconciliation steps succeeded - set Ready status and clear any stale Degraded condition
131        status_updater.set_condition(
132            "Ready",
133            "True",
134            "ReconcileSucceeded",
135            &format!(
136                "Zone {} configured on {} primary and {} secondary instance(s), discovered {} DNS record(s)",
137                zone_name, primary_count, secondary_count, records_count
138            ),
139        );
140        // Clear any stale Degraded condition from previous failures
141        status_updater.clear_degraded_condition();
142    }
143
144    // Apply all status changes in a single atomic operation
145    status_updater.apply(client).await?;
146
147    Ok(())
148}
149
150#[cfg(test)]
151#[path = "status_helpers_tests.rs"]
152mod status_helpers_tests;