bindy/bind9/types.rs
1// Copyright (c) 2025 Erick Bourgeois, firestoned
2// SPDX-License-Identifier: MIT
3
4//! Types and constants for BIND9 management.
5
6/// RNDC key data for authentication.
7#[derive(Clone)]
8pub struct RndcKeyData {
9 /// Key name (typically the instance name)
10 pub name: String,
11 /// HMAC algorithm
12 pub algorithm: crate::crd::RndcAlgorithm,
13 /// Base64-encoded secret key
14 pub secret: String,
15}
16
17// Custom Debug implementation to prevent logging the secret in cleartext
18impl std::fmt::Debug for RndcKeyData {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 f.debug_struct("RndcKeyData")
21 .field("name", &self.name)
22 .field("algorithm", &self.algorithm)
23 .field("secret", &"<redacted>")
24 .finish()
25 }
26}
27
28/// RNDC command error with structured information.
29///
30/// Parses BIND9 RNDC error responses in the format:
31/// ```text
32/// rndc: 'command' failed: error_type
33/// error details
34/// ```
35#[derive(Debug, Clone, thiserror::Error)]
36#[error("RNDC command '{command}' failed: {error}")]
37pub struct RndcError {
38 /// The RNDC command that failed (e.g., "zonestatus", "addzone")
39 pub command: String,
40 /// The error type (e.g., "not found", "already exists")
41 pub error: String,
42 /// Additional error details from BIND9
43 pub details: Option<String>,
44}
45
46impl RndcError {
47 /// Parse an RNDC error response.
48 ///
49 /// Expected format:
50 /// ```text
51 /// rndc: 'zonestatus' failed: not found
52 /// no matching zone 'example.com' in any view
53 /// ```
54 #[must_use]
55 pub fn parse(response: &str) -> Option<Self> {
56 // Parse first line: rndc: 'command' failed: error
57 let lines: Vec<&str> = response.lines().collect();
58 let first_line = lines.first()?;
59
60 if !first_line.starts_with("rndc:") {
61 return None;
62 }
63
64 // Extract command from 'command'
65 let command_start = first_line.find('\'')?;
66 let command_end = first_line[command_start + 1..].find('\'')?;
67 let command = first_line[command_start + 1..command_start + 1 + command_end].to_string();
68
69 // Extract error after "failed: "
70 let failed_pos = first_line.find("failed:")?;
71 let error = first_line[failed_pos + 7..].trim().to_string();
72
73 // Remaining lines are details
74 let details = if lines.len() > 1 {
75 Some(lines[1..].join("\n").trim().to_string())
76 } else {
77 None
78 };
79
80 Some(Self {
81 command,
82 error,
83 details,
84 })
85 }
86}
87
88/// Path to the `ServiceAccount` token file in Kubernetes pods
89pub const SERVICE_ACCOUNT_TOKEN_PATH: &str = "/var/run/secrets/kubernetes.io/serviceaccount/token";
90
91/// Parameters for creating SRV records.
92///
93/// Contains the priority, weight, port, and target required for SRV records.
94#[derive(Clone)]
95pub struct SRVRecordData {
96 /// Priority of the target host (lower is higher priority)
97 pub priority: i32,
98 /// Relative weight for records with the same priority
99 pub weight: i32,
100 /// TCP or UDP port on which the service is found
101 pub port: i32,
102 /// Canonical hostname of the machine providing the service
103 pub target: String,
104 /// Time to live in seconds
105 pub ttl: Option<i32>,
106}
107
108#[cfg(test)]
109#[path = "types_tests.rs"]
110mod types_tests;