bindy/
status_reasons.rs

1// Copyright (c) 2025 Erick Bourgeois, firestoned
2// SPDX-License-Identifier: MIT
3
4//! Standard Kubernetes status condition reasons for Bindy resources.
5//!
6//! This module defines constants for condition reasons following Kubernetes conventions.
7//! Reasons are programmatic identifiers in CamelCase that explain why a condition has
8//! a particular status.
9//!
10//! # Condition Hierarchy
11//!
12//! Bindy uses a hierarchical status tracking system where each resource tracks its children:
13//!
14//! - **`ClusterBind9Provider`** → tracks `Bind9Cluster` resources
15//! - **`Bind9Cluster`** → tracks `Bind9Instance` resources
16//! - **`Bind9Instance`** → tracks `Pod` replicas
17//!
18//! # Condition Types
19//!
20//! ## Primary Condition
21//!
22//! All resources have a single encompassing `type: Ready` condition that indicates
23//! the overall health of the resource.
24//!
25//! ## Child Conditions
26//!
27//! Resources also track individual child resource status with indexed conditions:
28//!
29//! - `Bind9Cluster`: conditions like `Bind9Instance-0`, `Bind9Instance-1`, etc.
30//! - `Bind9Instance`: conditions like `Pod-0`, `Pod-1`, etc.
31//!
32//! # Example Status
33//!
34//! ```yaml
35//! status:
36//!   conditions:
37//!     - type: Ready
38//!       status: "True"
39//!       reason: AllReady
40//!       message: "All 3 instances are ready"
41//!     - type: Bind9Instance-0
42//!       status: "True"
43//!       reason: AllReady
44//!       message: "Instance my-cluster-primary-0 is ready (2/2 pods)"
45//!     - type: Bind9Instance-1
46//!       status: "True"
47//!       reason: AllReady
48//!       message: "Instance my-cluster-primary-1 is ready (2/2 pods)"
49//!     - type: Bind9Instance-2
50//!       status: "False"
51//!       reason: PartiallyReady
52//!       message: "Instance my-cluster-secondary-0 is progressing (1/2 pods)"
53//! ```
54
55// ============================================================================
56// Common Reasons (All Resources)
57// ============================================================================
58
59/// All child resources are ready and healthy.
60///
61/// **Usage:**
62/// - Use for the **encompassing `type: Ready` condition** when all children are ready
63/// - For **child conditions** (e.g., `Bind9Instance-0`, `Pod-1`), use `REASON_READY` instead
64pub const REASON_ALL_READY: &str = "AllReady";
65
66/// Resource is ready and operational.
67///
68/// **Usage:**
69/// - Use for **child conditions** (e.g., `Bind9Instance-0`, `Pod-1`) when they are ready
70/// - For the **encompassing `type: Ready` condition**, use `REASON_ALL_READY` instead
71///
72/// **Example:**
73/// ```yaml
74/// conditions:
75///   - type: Ready                    # Encompassing condition
76///     status: "True"
77///     reason: AllReady               # <- Use REASON_ALL_READY
78///     message: "All 3 instances are ready"
79///
80///   - type: Bind9Instance-0          # Child condition
81///     status: "True"
82///     reason: Ready                  # <- Use REASON_READY
83///     message: "Instance my-cluster-primary-0 is ready (2/2 pods)"
84/// ```
85pub const REASON_READY: &str = "Ready";
86
87/// Some but not all child resources are ready.
88pub const REASON_PARTIALLY_READY: &str = "PartiallyReady";
89
90/// No child resources are ready.
91pub const REASON_NOT_READY: &str = "NotReady";
92
93/// No child resources found (expected children missing).
94pub const REASON_NO_CHILDREN: &str = "NoChildren";
95
96/// Resources are being created or updated.
97pub const REASON_PROGRESSING: &str = "Progressing";
98
99/// Configuration has been validated successfully.
100pub const REASON_CONFIGURATION_VALID: &str = "ConfigurationValid";
101
102/// Configuration validation failed.
103pub const REASON_CONFIGURATION_INVALID: &str = "ConfigurationInvalid";
104
105// ============================================================================
106// Bind9Instance Specific Reasons
107// ============================================================================
108
109/// Minimum number of replicas are available (but not all).
110///
111/// This indicates the instance has met its minimum replica threshold for availability
112/// but has not yet reached full desired replica count.
113pub const REASON_MINIMUM_REPLICAS_AVAILABLE: &str = "MinimumReplicasAvailable";
114
115/// Deployment has exceeded its progress deadline.
116///
117/// This occurs when a Deployment fails to make progress within its configured
118/// `progressDeadlineSeconds`. Common causes include image pull errors, insufficient
119/// resources, or crashing containers.
120pub const REASON_PROGRESS_DEADLINE_EXCEEDED: &str = "ProgressDeadlineExceeded";
121
122/// Failed to authenticate with RNDC (Remote Name Daemon Control).
123///
124/// This indicates the RNDC key authentication failed when attempting to connect
125/// to the BIND9 server. Possible causes:
126/// - Incorrect RNDC key in Secret
127/// - RNDC key mismatch between controller and server
128/// - RNDC disabled in BIND9 configuration
129pub const REASON_RNDC_AUTHENTICATION_FAILED: &str = "RNDCAuthenticationFailed";
130
131/// Cannot connect to Bindcar API container.
132///
133/// This indicates the Bindcar sidecar container is unreachable via HTTP.
134/// Maps to HTTP connection errors or gateway errors (502, 503, 504).
135///
136/// Possible causes:
137/// - Bindcar container not running
138/// - Network policy blocking traffic
139/// - Bindcar listening on wrong port
140/// - Bindcar container crashed
141/// - Gateway timeout reaching the pod
142pub const REASON_BINDCAR_UNREACHABLE: &str = "BindcarUnreachable";
143
144/// Secondary instance successfully transferred zones from primary.
145///
146/// This confirms that an AXFR (full zone transfer) or IXFR (incremental zone transfer)
147/// completed successfully from the primary server to this secondary instance.
148pub const REASON_ZONE_TRANSFER_COMPLETE: &str = "ZoneTransferComplete";
149
150/// Zone transfer failed or is in progress.
151///
152/// This indicates a zone transfer (AXFR/IXFR) failed or has not completed yet.
153/// Common causes:
154/// - Primary server unreachable
155/// - TSIG authentication failure
156/// - Network issues
157/// - Zone serial number mismatch
158pub const REASON_ZONE_TRANSFER_FAILED: &str = "ZoneTransferFailed";
159
160/// Deployment is waiting for pods to be scheduled.
161pub const REASON_PODS_PENDING: &str = "PodsPending";
162
163/// One or more pods are crashing or in `CrashLoopBackOff`.
164pub const REASON_PODS_CRASHING: &str = "PodsCrashing";
165
166/// Bindcar API returned an invalid or malformed request error.
167///
168/// Maps to HTTP 400 Bad Request.
169/// This indicates the request sent to Bindcar was malformed or contained
170/// invalid parameters. This is typically a bug in the controller.
171pub const REASON_BINDCAR_BAD_REQUEST: &str = "BindcarBadRequest";
172
173/// Bindcar API authentication or authorization failed.
174///
175/// Maps to HTTP 401 Unauthorized or 403 Forbidden.
176/// This indicates the controller lacks proper credentials or permissions
177/// to interact with the Bindcar API.
178pub const REASON_BINDCAR_AUTH_FAILED: &str = "BindcarAuthFailed";
179
180/// Requested zone or resource not found in BIND9.
181///
182/// Maps to HTTP 404 Not Found.
183/// This indicates the zone, record, or resource does not exist in the
184/// BIND9 server configuration.
185pub const REASON_ZONE_NOT_FOUND: &str = "ZoneNotFound";
186
187/// Bindcar API encountered an internal server error.
188///
189/// Maps to HTTP 500 Internal Server Error.
190/// This indicates the Bindcar API encountered an unexpected error while
191/// processing the request. Check Bindcar container logs for details.
192pub const REASON_BINDCAR_INTERNAL_ERROR: &str = "BindcarInternalError";
193
194/// Bindcar API feature not implemented.
195///
196/// Maps to HTTP 501 Not Implemented.
197/// This indicates the requested operation is not supported by the current
198/// version of Bindcar.
199pub const REASON_BINDCAR_NOT_IMPLEMENTED: &str = "BindcarNotImplemented";
200
201/// Gateway error reaching Bindcar pod.
202///
203/// Maps to HTTP 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout.
204/// This indicates a network gateway or load balancer cannot reach the Bindcar
205/// pod, even though the pod might be running. Check network policies, service
206/// mesh configuration, and pod readiness probes.
207pub const REASON_GATEWAY_ERROR: &str = "GatewayError";
208
209// ============================================================================
210// Bind9Cluster Specific Reasons
211// ============================================================================
212
213/// All managed `Bind9Instance` resources have been created.
214pub const REASON_INSTANCES_CREATED: &str = "InstancesCreated";
215
216/// Scaling instances up or down to match desired replica count.
217pub const REASON_INSTANCES_SCALING: &str = "InstancesScaling";
218
219/// Waiting for instances to be created or updated.
220pub const REASON_INSTANCES_PENDING: &str = "InstancesPending";
221
222// ============================================================================
223// ClusterBind9Provider Specific Reasons
224// ============================================================================
225
226/// All namespace-scoped `Bind9Cluster` resources are ready.
227pub const REASON_CLUSTERS_READY: &str = "ClustersReady";
228
229/// Some namespace-scoped `Bind9Cluster` resources are not ready.
230pub const REASON_CLUSTERS_PROGRESSING: &str = "ClustersProgressing";
231
232// ============================================================================
233// Network and External Service Reasons
234// ============================================================================
235
236/// Cannot reach upstream or external services.
237///
238/// This is used when a secondary instance cannot reach its configured primary servers
239/// for zone transfers, or when any resource cannot reach required external dependencies.
240pub const REASON_UPSTREAM_UNREACHABLE: &str = "UpstreamUnreachable";
241
242// ============================================================================
243// Condition Types
244// ============================================================================
245
246/// Primary condition type indicating overall resource readiness.
247pub const CONDITION_TYPE_READY: &str = "Ready";
248
249/// Condition type prefix for tracking individual `Bind9Instance` children.
250///
251/// Format: `Bind9Instance-{index}` (e.g., "Bind9Instance-0", "Bind9Instance-1")
252pub const CONDITION_TYPE_BIND9_INSTANCE_PREFIX: &str = "Bind9Instance";
253
254/// Condition type prefix for tracking individual `Pod` children.
255///
256/// Format: `Pod-{index}` (e.g., "Pod-0", "Pod-1")
257pub const CONDITION_TYPE_POD_PREFIX: &str = "Pod";
258
259// ============================================================================
260// Helper Functions
261// ============================================================================
262
263/// Create a condition type for a specific `Bind9Instance` child.
264///
265/// # Arguments
266///
267/// * `index` - The index of the `Bind9Instance` (e.g., 0, 1, 2)
268///
269/// # Returns
270///
271/// A condition type string like "Bind9Instance-0"
272///
273/// # Example
274///
275/// ```rust
276/// use bindy::status_reasons::bind9_instance_condition_type;
277///
278/// let condition_type = bind9_instance_condition_type(0);
279/// assert_eq!(condition_type, "Bind9Instance-0");
280/// ```
281#[must_use]
282pub fn bind9_instance_condition_type(index: usize) -> String {
283    format!("{CONDITION_TYPE_BIND9_INSTANCE_PREFIX}-{index}")
284}
285
286/// Create a condition type for a specific Pod child.
287///
288/// # Arguments
289///
290/// * `index` - The index of the Pod (e.g., 0, 1, 2)
291///
292/// # Returns
293///
294/// A condition type string like "Pod-0"
295///
296/// # Example
297///
298/// ```rust
299/// use bindy::status_reasons::pod_condition_type;
300///
301/// let condition_type = pod_condition_type(0);
302/// assert_eq!(condition_type, "Pod-0");
303/// ```
304#[must_use]
305pub fn pod_condition_type(index: usize) -> String {
306    format!("{CONDITION_TYPE_POD_PREFIX}-{index}")
307}
308
309/// Extract the index from a child condition type.
310///
311/// # Arguments
312///
313/// * `condition_type` - A condition type like "Bind9Instance-0" or "Pod-2"
314///
315/// # Returns
316///
317/// The index as `Option<usize>`, or `None` if the format is invalid.
318///
319/// # Example
320///
321/// ```rust
322/// use bindy::status_reasons::extract_child_index;
323///
324/// assert_eq!(extract_child_index("Bind9Instance-0"), Some(0));
325/// assert_eq!(extract_child_index("Pod-5"), Some(5));
326/// assert_eq!(extract_child_index("Ready"), None);
327/// assert_eq!(extract_child_index("Invalid-Format"), None);
328/// ```
329#[must_use]
330pub fn extract_child_index(condition_type: &str) -> Option<usize> {
331    condition_type
332        .rsplit_once('-')
333        .and_then(|(_, index_str)| index_str.parse().ok())
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339
340    #[test]
341    fn test_bind9_instance_condition_type() {
342        assert_eq!(bind9_instance_condition_type(0), "Bind9Instance-0");
343        assert_eq!(bind9_instance_condition_type(5), "Bind9Instance-5");
344        assert_eq!(bind9_instance_condition_type(99), "Bind9Instance-99");
345    }
346
347    #[test]
348    fn test_pod_condition_type() {
349        assert_eq!(pod_condition_type(0), "Pod-0");
350        assert_eq!(pod_condition_type(3), "Pod-3");
351        assert_eq!(pod_condition_type(10), "Pod-10");
352    }
353
354    #[test]
355    fn test_extract_child_index() {
356        // Valid formats
357        assert_eq!(extract_child_index("Bind9Instance-0"), Some(0));
358        assert_eq!(extract_child_index("Bind9Instance-15"), Some(15));
359        assert_eq!(extract_child_index("Pod-0"), Some(0));
360        assert_eq!(extract_child_index("Pod-9"), Some(9));
361
362        // Invalid formats
363        assert_eq!(extract_child_index("Ready"), None);
364        assert_eq!(extract_child_index("Bind9Instance"), None);
365        assert_eq!(extract_child_index("Bind9Instance-"), None);
366        assert_eq!(extract_child_index("Bind9Instance-abc"), None);
367        assert_eq!(extract_child_index(""), None);
368    }
369}