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;