docker_wrapper/compose/
kill.rs

1//! Docker Compose kill command implementation.
2
3use crate::compose::{ComposeCommandV2 as ComposeCommand, ComposeConfig};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Docker Compose kill command
8///
9/// Force stop service containers.
10#[derive(Debug, Clone, Default)]
11pub struct ComposeKillCommand {
12    /// Base configuration
13    pub config: ComposeConfig,
14    /// Signal to send (default: SIGKILL)
15    pub signal: Option<String>,
16    /// Remove containers after killing
17    pub remove_orphans: bool,
18    /// Services to kill
19    pub services: Vec<String>,
20}
21
22/// Result from kill command
23#[derive(Debug, Clone)]
24pub struct KillResult {
25    /// Output from the command
26    pub output: String,
27    /// Whether the operation succeeded
28    pub success: bool,
29}
30
31impl ComposeKillCommand {
32    /// Create a new kill command
33    #[must_use]
34    pub fn new() -> Self {
35        Self::default()
36    }
37
38    /// Add a compose file
39    #[must_use]
40    pub fn file<P: Into<std::path::PathBuf>>(mut self, file: P) -> Self {
41        self.config.files.push(file.into());
42        self
43    }
44
45    /// Set project name
46    #[must_use]
47    pub fn project_name(mut self, name: impl Into<String>) -> Self {
48        self.config.project_name = Some(name.into());
49        self
50    }
51
52    /// Set signal to send
53    #[must_use]
54    pub fn signal(mut self, signal: impl Into<String>) -> Self {
55        self.signal = Some(signal.into());
56        self
57    }
58
59    /// Remove orphaned containers
60    #[must_use]
61    pub fn remove_orphans(mut self) -> Self {
62        self.remove_orphans = true;
63        self
64    }
65
66    /// Add a service to kill
67    #[must_use]
68    pub fn service(mut self, service: impl Into<String>) -> Self {
69        self.services.push(service.into());
70        self
71    }
72
73    /// Add multiple services to kill
74    #[must_use]
75    pub fn services<I, S>(mut self, services: I) -> Self
76    where
77        I: IntoIterator<Item = S>,
78        S: Into<String>,
79    {
80        self.services.extend(services.into_iter().map(Into::into));
81        self
82    }
83
84    fn build_args(&self) -> Vec<String> {
85        let mut args = vec!["kill".to_string()];
86
87        // Add signal
88        if let Some(signal) = &self.signal {
89            args.push("--signal".to_string());
90            args.push(signal.clone());
91        }
92
93        // Add flags
94        if self.remove_orphans {
95            args.push("--remove-orphans".to_string());
96        }
97
98        // Add services
99        args.extend(self.services.clone());
100
101        args
102    }
103}
104
105#[async_trait]
106impl ComposeCommand for ComposeKillCommand {
107    type Output = KillResult;
108
109    fn get_config(&self) -> &ComposeConfig {
110        &self.config
111    }
112
113    fn get_config_mut(&mut self) -> &mut ComposeConfig {
114        &mut self.config
115    }
116
117    async fn execute_compose(&self, args: Vec<String>) -> Result<Self::Output> {
118        let output = self.execute_compose_command(args).await?;
119
120        Ok(KillResult {
121            output: output.stdout,
122            success: output.success,
123        })
124    }
125
126    async fn execute(&self) -> Result<Self::Output> {
127        let args = self.build_args();
128        self.execute_compose(args).await
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_kill_command_basic() {
138        let cmd = ComposeKillCommand::new();
139        let args = cmd.build_args();
140        assert_eq!(args[0], "kill");
141    }
142
143    #[test]
144    fn test_kill_command_with_signal() {
145        let cmd = ComposeKillCommand::new().signal("SIGTERM");
146        let args = cmd.build_args();
147        assert!(args.contains(&"--signal".to_string()));
148        assert!(args.contains(&"SIGTERM".to_string()));
149    }
150
151    #[test]
152    fn test_kill_command_with_services() {
153        let cmd = ComposeKillCommand::new()
154            .service("web")
155            .service("worker")
156            .remove_orphans();
157        let args = cmd.build_args();
158        assert!(args.contains(&"web".to_string()));
159        assert!(args.contains(&"worker".to_string()));
160        assert!(args.contains(&"--remove-orphans".to_string()));
161    }
162}