docker_wrapper/command/
restart.rs1use super::{CommandExecutor, DockerCommand};
7use crate::error::{Error, Result};
8use async_trait::async_trait;
9use std::time::Duration;
10
11#[derive(Debug, Clone)]
13pub struct RestartCommand {
14 pub executor: CommandExecutor,
16 containers: Vec<String>,
18 signal: Option<String>,
20 timeout: Option<u32>,
22}
23
24#[derive(Debug, Clone, PartialEq)]
26pub struct RestartResult {
27 pub stdout: String,
29 pub stderr: String,
31 pub restarted_containers: Vec<String>,
33}
34
35impl RestartCommand {
36 pub fn new(container: impl Into<String>) -> Self {
52 Self {
53 executor: CommandExecutor::new(),
54 containers: vec![container.into()],
55 signal: None,
56 timeout: None,
57 }
58 }
59
60 pub fn new_multiple<I, S>(containers: I) -> Self
70 where
71 I: IntoIterator<Item = S>,
72 S: Into<String>,
73 {
74 Self {
75 executor: CommandExecutor::new(),
76 containers: containers.into_iter().map(Into::into).collect(),
77 signal: None,
78 timeout: None,
79 }
80 }
81
82 #[must_use]
93 pub fn signal(mut self, signal: impl Into<String>) -> Self {
94 self.signal = Some(signal.into());
95 self
96 }
97
98 #[must_use]
109 pub fn timeout(mut self, timeout: u32) -> Self {
110 self.timeout = Some(timeout);
111 self
112 }
113
114 #[must_use]
126 #[allow(clippy::cast_possible_truncation)]
127 pub fn timeout_duration(mut self, timeout: Duration) -> Self {
128 self.timeout = Some(timeout.as_secs().min(u64::from(u32::MAX)) as u32);
129 self
130 }
131}
132
133#[async_trait]
134impl DockerCommand for RestartCommand {
135 type Output = RestartResult;
136
137 fn get_executor(&self) -> &CommandExecutor {
138 &self.executor
139 }
140
141 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
142 &mut self.executor
143 }
144
145 fn build_command_args(&self) -> Vec<String> {
146 let mut args = vec!["restart".to_string()];
147
148 if let Some(signal) = &self.signal {
150 args.push("--signal".to_string());
151 args.push(signal.clone());
152 }
153
154 if let Some(timeout) = self.timeout {
156 args.push("--timeout".to_string());
157 args.push(timeout.to_string());
158 }
159
160 args.extend(self.containers.clone());
162
163 args.extend(self.executor.raw_args.clone());
165
166 args
167 }
168
169 async fn execute(&self) -> Result<Self::Output> {
170 if self.containers.is_empty() {
171 return Err(Error::invalid_config("No containers specified"));
172 }
173
174 let args = self.build_command_args();
175 let output = self.execute_command(args).await?;
176
177 let restarted_containers = if output.stdout.trim().is_empty() {
179 self.containers.clone()
181 } else {
182 output
184 .stdout
185 .lines()
186 .filter(|line| !line.trim().is_empty())
187 .map(|line| line.trim().to_string())
188 .collect()
189 };
190
191 Ok(RestartResult {
192 stdout: output.stdout,
193 stderr: output.stderr,
194 restarted_containers,
195 })
196 }
197}
198
199impl RestartCommand {
200 #[must_use]
202 pub fn args(&self) -> Vec<String> {
203 self.build_command_args()
204 }
205}
206
207impl RestartResult {
208 #[must_use]
210 pub fn is_success(&self) -> bool {
211 !self.restarted_containers.is_empty()
212 }
213
214 #[must_use]
216 pub fn container_count(&self) -> usize {
217 self.restarted_containers.len()
218 }
219
220 #[must_use]
222 pub fn first_container(&self) -> Option<&String> {
223 self.restarted_containers.first()
224 }
225
226 #[must_use]
228 pub fn contains_container(&self, container: &str) -> bool {
229 self.restarted_containers.iter().any(|c| c == container)
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_restart_command_new() {
239 let cmd = RestartCommand::new("test-container");
240 assert_eq!(cmd.containers, vec!["test-container"]);
241 assert!(cmd.signal.is_none());
242 assert!(cmd.timeout.is_none());
243 }
244
245 #[test]
246 fn test_restart_command_new_multiple() {
247 let cmd = RestartCommand::new_multiple(vec!["container1", "container2"]);
248 assert_eq!(cmd.containers, vec!["container1", "container2"]);
249 }
250
251 #[test]
252 fn test_restart_command_with_signal() {
253 let cmd = RestartCommand::new("test-container").signal("SIGKILL");
254 assert_eq!(cmd.signal, Some("SIGKILL".to_string()));
255 }
256
257 #[test]
258 fn test_restart_command_with_timeout() {
259 let cmd = RestartCommand::new("test-container").timeout(30);
260 assert_eq!(cmd.timeout, Some(30));
261 }
262
263 #[test]
264 fn test_restart_command_with_timeout_duration() {
265 let cmd = RestartCommand::new("test-container").timeout_duration(Duration::from_secs(45));
266 assert_eq!(cmd.timeout, Some(45));
267 }
268
269 #[test]
270 fn test_restart_command_args_basic() {
271 let cmd = RestartCommand::new("test-container");
272 let args = cmd.args();
273 assert_eq!(args, vec!["restart", "test-container"]);
274 }
275
276 #[test]
277 fn test_restart_command_args_with_options() {
278 let cmd = RestartCommand::new("test-container")
279 .signal("SIGTERM")
280 .timeout(30);
281 let args = cmd.args();
282 assert_eq!(
283 args,
284 vec![
285 "restart",
286 "--signal",
287 "SIGTERM",
288 "--timeout",
289 "30",
290 "test-container"
291 ]
292 );
293 }
294
295 #[test]
296 fn test_restart_command_args_multiple_containers() {
297 let cmd = RestartCommand::new_multiple(vec!["container1", "container2"]).timeout(10);
298 let args = cmd.args();
299 assert_eq!(
300 args,
301 vec!["restart", "--timeout", "10", "container1", "container2"]
302 );
303 }
304
305 #[test]
306 fn test_restart_result_is_success() {
307 let result = RestartResult {
308 stdout: "container1\n".to_string(),
309 stderr: String::new(),
310 restarted_containers: vec!["container1".to_string()],
311 };
312 assert!(result.is_success());
313
314 let empty_result = RestartResult {
315 stdout: String::new(),
316 stderr: String::new(),
317 restarted_containers: vec![],
318 };
319 assert!(!empty_result.is_success());
320 }
321
322 #[test]
323 fn test_restart_result_container_count() {
324 let result = RestartResult {
325 stdout: String::new(),
326 stderr: String::new(),
327 restarted_containers: vec!["container1".to_string(), "container2".to_string()],
328 };
329 assert_eq!(result.container_count(), 2);
330 }
331
332 #[test]
333 fn test_restart_result_first_container() {
334 let result = RestartResult {
335 stdout: String::new(),
336 stderr: String::new(),
337 restarted_containers: vec!["container1".to_string(), "container2".to_string()],
338 };
339 assert_eq!(result.first_container(), Some(&"container1".to_string()));
340
341 let empty_result = RestartResult {
342 stdout: String::new(),
343 stderr: String::new(),
344 restarted_containers: vec![],
345 };
346 assert_eq!(empty_result.first_container(), None);
347 }
348
349 #[test]
350 fn test_restart_result_contains_container() {
351 let result = RestartResult {
352 stdout: String::new(),
353 stderr: String::new(),
354 restarted_containers: vec!["container1".to_string(), "container2".to_string()],
355 };
356 assert!(result.contains_container("container1"));
357 assert!(result.contains_container("container2"));
358 assert!(!result.contains_container("container3"));
359 }
360
361 #[test]
362 fn test_command_name() {
363 let cmd = RestartCommand::new("test");
364 let args = cmd.build_command_args();
365 assert_eq!(args[0], "restart");
366 }
367}