Skip to main content

torrust_tracker_deployer_lib/domain/backup/
mod.rs

1//! Backup domain types for the Torrust Tracker Deployer.
2//!
3//! This module contains domain types related to backup configuration:
4//! - `CronSchedule`: Validated cron schedule expression
5//! - `RetentionDays`: Number of days to retain backups
6//! - `BackupConfig`: Complete backup configuration
7
8mod cron_schedule;
9mod retention_days;
10
11pub use cron_schedule::CronSchedule;
12pub use retention_days::RetentionDays;
13
14use serde::{Deserialize, Serialize};
15
16use crate::domain::topology::{
17    DependencyCondition, EnabledServices, Network, NetworkDerivation, PortBinding, PortDerivation,
18    Service, ServiceDependency,
19};
20
21// Re-export the trait so users can import it from this module
22pub use crate::domain::topology::traits::DependencyDerivation;
23
24/// Backup configuration for a deployed tracker instance.
25///
26/// Specifies when backups run (cron schedule) and how long to keep them (retention).
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct BackupConfig {
29    /// Cron schedule for when backups should run (e.g., "0 3 * * *" for 3:00 AM daily).
30    schedule: CronSchedule,
31
32    /// Number of days to retain backups before deletion.
33    retention_days: RetentionDays,
34}
35
36impl BackupConfig {
37    /// Creates a new backup configuration.
38    ///
39    /// # Arguments
40    ///
41    /// * `schedule` - Validated cron schedule
42    /// * `retention_days` - Number of days to keep backups
43    #[must_use]
44    pub const fn new(schedule: CronSchedule, retention_days: RetentionDays) -> Self {
45        Self {
46            schedule,
47            retention_days,
48        }
49    }
50
51    /// Returns the cron schedule.
52    #[must_use]
53    pub const fn schedule(&self) -> &CronSchedule {
54        &self.schedule
55    }
56
57    /// Returns the retention period in days.
58    #[must_use]
59    pub const fn retention_days(&self) -> &RetentionDays {
60        &self.retention_days
61    }
62}
63
64impl Default for BackupConfig {
65    /// Default backup configuration:
66    /// - Schedule: 3:00 AM daily ("0 3 * * *")
67    /// - Retention: 7 days
68    fn default() -> Self {
69        Self {
70            schedule: CronSchedule::default(),
71            retention_days: RetentionDays::default(),
72        }
73    }
74}
75
76// =============================================================================
77// Topology Trait Implementations
78// =============================================================================
79
80impl PortDerivation for BackupConfig {
81    /// Backup service exposes no ports
82    ///
83    /// The backup container runs as a one-shot service and doesn't listen
84    /// on any network ports.
85    fn derive_ports(&self) -> Vec<PortBinding> {
86        vec![]
87    }
88}
89
90impl NetworkDerivation for BackupConfig {
91    /// Backup connects to Database network when `MySQL` is enabled
92    ///
93    /// When `MySQL` is the database driver, the backup container needs access
94    /// to the database network to connect to `MySQL` for database dumps.
95    /// For `SQLite`, no network access is needed (file access via volume).
96    fn derive_networks(&self, enabled_services: &EnabledServices) -> Vec<Network> {
97        if enabled_services.has(Service::MySQL) {
98            vec![Network::Database]
99        } else {
100            vec![]
101        }
102    }
103}
104
105impl DependencyDerivation for BackupConfig {
106    /// Backup depends on `MySQL` service being healthy when `MySQL` is enabled
107    ///
108    /// When `MySQL` is the database driver, the backup must wait for `MySQL`
109    /// to be ready before attempting database dumps.
110    /// For `SQLite`, no external dependencies are needed.
111    fn derive_dependencies(&self, enabled_services: &EnabledServices) -> Vec<ServiceDependency> {
112        if enabled_services.has(Service::MySQL) {
113            vec![ServiceDependency {
114                service: Service::MySQL,
115                condition: DependencyCondition::ServiceHealthy,
116            }]
117        } else {
118            vec![]
119        }
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn it_should_create_backup_config_with_valid_values() {
129        let schedule = CronSchedule::new("0 3 * * *".to_string()).expect("valid cron schedule");
130        let retention = RetentionDays::new(7).expect("valid retention days");
131
132        let config = BackupConfig::new(schedule.clone(), retention);
133
134        assert_eq!(config.schedule(), &schedule);
135        assert_eq!(config.retention_days(), &retention);
136    }
137
138    #[test]
139    fn it_should_provide_sensible_defaults() {
140        let config = BackupConfig::default();
141
142        assert_eq!(
143            config.schedule().as_str(),
144            "0 3 * * *",
145            "default schedule should be 3:00 AM daily"
146        );
147        assert_eq!(
148            config.retention_days().as_u32(),
149            7,
150            "default retention should be 7 days"
151        );
152    }
153
154    #[test]
155    fn it_should_serialize_and_deserialize_correctly() {
156        let config = BackupConfig::default();
157
158        let json = serde_json::to_string(&config).expect("serialization should succeed");
159        let deserialized: BackupConfig =
160            serde_json::from_str(&json).expect("deserialization should succeed");
161
162        assert_eq!(config, deserialized);
163    }
164}