docker_wrapper/command/swarm/
update.rs

1//! Docker swarm update command implementation.
2
3use crate::command::{CommandExecutor, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Result of swarm update command
8#[derive(Debug, Clone)]
9pub struct SwarmUpdateResult {
10    /// Whether the update was successful
11    pub success: bool,
12    /// Raw output from the command
13    pub output: String,
14}
15
16/// Docker swarm update command builder
17///
18/// Update swarm configuration.
19#[derive(Debug, Clone, Default)]
20pub struct SwarmUpdateCommand {
21    /// Enable or disable autolock
22    autolock: Option<bool>,
23    /// Validity period for node certificates (ns|us|ms|s|m|h)
24    cert_expiry: Option<String>,
25    /// Dispatcher heartbeat period (ns|us|ms|s|m|h)
26    dispatcher_heartbeat: Option<String>,
27    /// Specifications of external CA to use
28    external_ca: Option<String>,
29    /// Number of snapshots to keep beyond current snapshot
30    max_snapshots: Option<u32>,
31    /// Number of log entries to trigger a snapshot
32    snapshot_interval: Option<u32>,
33    /// Task history retention limit
34    task_history_limit: Option<i32>,
35    /// Command executor
36    pub executor: CommandExecutor,
37}
38
39impl SwarmUpdateCommand {
40    /// Create a new swarm update command
41    #[must_use]
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// Enable autolock
47    #[must_use]
48    pub fn autolock(mut self, enabled: bool) -> Self {
49        self.autolock = Some(enabled);
50        self
51    }
52
53    /// Set the certificate expiry duration
54    #[must_use]
55    pub fn cert_expiry(mut self, expiry: impl Into<String>) -> Self {
56        self.cert_expiry = Some(expiry.into());
57        self
58    }
59
60    /// Set the dispatcher heartbeat period
61    #[must_use]
62    pub fn dispatcher_heartbeat(mut self, heartbeat: impl Into<String>) -> Self {
63        self.dispatcher_heartbeat = Some(heartbeat.into());
64        self
65    }
66
67    /// Set external CA specifications
68    #[must_use]
69    pub fn external_ca(mut self, spec: impl Into<String>) -> Self {
70        self.external_ca = Some(spec.into());
71        self
72    }
73
74    /// Set the maximum number of snapshots to keep
75    #[must_use]
76    pub fn max_snapshots(mut self, count: u32) -> Self {
77        self.max_snapshots = Some(count);
78        self
79    }
80
81    /// Set the snapshot interval
82    #[must_use]
83    pub fn snapshot_interval(mut self, interval: u32) -> Self {
84        self.snapshot_interval = Some(interval);
85        self
86    }
87
88    /// Set the task history retention limit
89    #[must_use]
90    pub fn task_history_limit(mut self, limit: i32) -> Self {
91        self.task_history_limit = Some(limit);
92        self
93    }
94
95    /// Build the command arguments
96    fn build_args(&self) -> Vec<String> {
97        let mut args = vec!["swarm".to_string(), "update".to_string()];
98
99        if let Some(enabled) = self.autolock {
100            args.push("--autolock".to_string());
101            args.push(enabled.to_string());
102        }
103
104        if let Some(ref expiry) = self.cert_expiry {
105            args.push("--cert-expiry".to_string());
106            args.push(expiry.clone());
107        }
108
109        if let Some(ref heartbeat) = self.dispatcher_heartbeat {
110            args.push("--dispatcher-heartbeat".to_string());
111            args.push(heartbeat.clone());
112        }
113
114        if let Some(ref spec) = self.external_ca {
115            args.push("--external-ca".to_string());
116            args.push(spec.clone());
117        }
118
119        if let Some(count) = self.max_snapshots {
120            args.push("--max-snapshots".to_string());
121            args.push(count.to_string());
122        }
123
124        if let Some(interval) = self.snapshot_interval {
125            args.push("--snapshot-interval".to_string());
126            args.push(interval.to_string());
127        }
128
129        if let Some(limit) = self.task_history_limit {
130            args.push("--task-history-limit".to_string());
131            args.push(limit.to_string());
132        }
133
134        args
135    }
136}
137
138#[async_trait]
139impl DockerCommand for SwarmUpdateCommand {
140    type Output = SwarmUpdateResult;
141
142    fn get_executor(&self) -> &CommandExecutor {
143        &self.executor
144    }
145
146    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
147        &mut self.executor
148    }
149
150    fn build_command_args(&self) -> Vec<String> {
151        self.build_args()
152    }
153
154    async fn execute(&self) -> Result<Self::Output> {
155        let args = self.build_args();
156        let output = self.execute_command(args).await?;
157        Ok(SwarmUpdateResult {
158            success: true,
159            output: output.stdout,
160        })
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_swarm_update_basic() {
170        let cmd = SwarmUpdateCommand::new();
171        let args = cmd.build_args();
172        assert_eq!(args, vec!["swarm", "update"]);
173    }
174
175    #[test]
176    fn test_swarm_update_autolock_enable() {
177        let cmd = SwarmUpdateCommand::new().autolock(true);
178        let args = cmd.build_args();
179        assert!(args.contains(&"--autolock".to_string()));
180        assert!(args.contains(&"true".to_string()));
181    }
182
183    #[test]
184    fn test_swarm_update_autolock_disable() {
185        let cmd = SwarmUpdateCommand::new().autolock(false);
186        let args = cmd.build_args();
187        assert!(args.contains(&"--autolock".to_string()));
188        assert!(args.contains(&"false".to_string()));
189    }
190
191    #[test]
192    fn test_swarm_update_cert_expiry() {
193        let cmd = SwarmUpdateCommand::new().cert_expiry("90d");
194        let args = cmd.build_args();
195        assert!(args.contains(&"--cert-expiry".to_string()));
196        assert!(args.contains(&"90d".to_string()));
197    }
198
199    #[test]
200    fn test_swarm_update_all_options() {
201        let cmd = SwarmUpdateCommand::new()
202            .autolock(true)
203            .cert_expiry("90d")
204            .dispatcher_heartbeat("5s")
205            .external_ca("protocol=cfssl,url=https://ca.example.com")
206            .max_snapshots(5)
207            .snapshot_interval(10000)
208            .task_history_limit(10);
209
210        let args = cmd.build_args();
211        assert!(args.contains(&"--autolock".to_string()));
212        assert!(args.contains(&"--cert-expiry".to_string()));
213        assert!(args.contains(&"--dispatcher-heartbeat".to_string()));
214        assert!(args.contains(&"--external-ca".to_string()));
215        assert!(args.contains(&"--max-snapshots".to_string()));
216        assert!(args.contains(&"--snapshot-interval".to_string()));
217        assert!(args.contains(&"--task-history-limit".to_string()));
218    }
219}