docker_wrapper/command/
kill.rs

1//! Docker kill command implementation.
2//!
3//! This module provides the `docker kill` command for sending signals to running containers.
4
5use super::{CommandExecutor, CommandOutput, DockerCommand};
6use crate::error::{Error, Result};
7use async_trait::async_trait;
8
9/// Docker kill command builder
10#[derive(Debug, Clone)]
11pub struct KillCommand {
12    /// Container names or IDs to kill
13    containers: Vec<String>,
14    /// Signal to send (default: SIGKILL)
15    signal: Option<String>,
16    /// Command executor
17    pub executor: CommandExecutor,
18}
19
20impl KillCommand {
21    /// Create a new kill command for a single container
22    #[must_use]
23    pub fn new(container: impl Into<String>) -> Self {
24        Self {
25            containers: vec![container.into()],
26            signal: None,
27            executor: CommandExecutor::new(),
28        }
29    }
30
31    /// Create a new kill command for multiple containers
32    #[must_use]
33    pub fn new_multiple(containers: Vec<impl Into<String>>) -> Self {
34        Self {
35            containers: containers.into_iter().map(Into::into).collect(),
36            signal: None,
37            executor: CommandExecutor::new(),
38        }
39    }
40
41    /// Add another container to kill
42    #[must_use]
43    pub fn container(mut self, container: impl Into<String>) -> Self {
44        self.containers.push(container.into());
45        self
46    }
47
48    /// Set the signal to send (e.g., "SIGTERM", "SIGKILL", "9")
49    #[must_use]
50    pub fn signal(mut self, signal: impl Into<String>) -> Self {
51        self.signal = Some(signal.into());
52        self
53    }
54
55    /// Execute the kill command
56    ///
57    /// # Errors
58    /// Returns an error if:
59    /// - No containers are specified
60    /// - The Docker daemon is not running
61    /// - Any of the specified containers don't exist
62    /// - The signal is invalid
63    pub async fn run(&self) -> Result<KillResult> {
64        let output = self.execute().await?;
65
66        // Parse killed container IDs from output
67        let killed: Vec<String> = output
68            .stdout
69            .lines()
70            .filter(|line| !line.is_empty())
71            .map(String::from)
72            .collect();
73
74        Ok(KillResult {
75            killed,
76            signal: self.signal.clone(),
77            output,
78        })
79    }
80}
81
82#[async_trait]
83impl DockerCommand for KillCommand {
84    type Output = CommandOutput;
85
86    fn build_command_args(&self) -> Vec<String> {
87        let mut args = vec!["kill".to_string()];
88
89        if let Some(ref sig) = self.signal {
90            args.push("--signal".to_string());
91            args.push(sig.clone());
92        }
93
94        // Add container names/IDs
95        args.extend(self.containers.clone());
96
97        args.extend(self.executor.raw_args.clone());
98        args
99    }
100
101    fn get_executor(&self) -> &CommandExecutor {
102        &self.executor
103    }
104
105    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
106        &mut self.executor
107    }
108
109    async fn execute(&self) -> Result<Self::Output> {
110        if self.containers.is_empty() {
111            return Err(Error::invalid_config("No containers specified for kill"));
112        }
113
114        let args = self.build_command_args();
115        let command_name = args[0].clone();
116        let command_args = args[1..].to_vec();
117        self.executor
118            .execute_command(&command_name, command_args)
119            .await
120    }
121}
122
123/// Result from the kill command
124#[derive(Debug, Clone)]
125pub struct KillResult {
126    /// List of killed container IDs
127    pub killed: Vec<String>,
128    /// Signal that was sent
129    pub signal: Option<String>,
130    /// Raw command output
131    pub output: CommandOutput,
132}
133
134impl KillResult {
135    /// Check if all containers were killed successfully
136    #[must_use]
137    pub fn all_killed(&self) -> bool {
138        self.output.success
139    }
140
141    /// Get the number of containers killed
142    #[must_use]
143    pub fn count(&self) -> usize {
144        self.killed.len()
145    }
146
147    /// Get the signal that was sent
148    #[must_use]
149    pub fn signal_sent(&self) -> &str {
150        self.signal.as_deref().unwrap_or("SIGKILL")
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_kill_single_container() {
160        let cmd = KillCommand::new("test-container");
161        let args = cmd.build_command_args();
162        assert_eq!(args, vec!["kill", "test-container"]);
163    }
164
165    #[test]
166    fn test_kill_multiple_containers() {
167        let cmd = KillCommand::new_multiple(vec!["container1", "container2", "container3"]);
168        let args = cmd.build_command_args();
169        assert_eq!(args, vec!["kill", "container1", "container2", "container3"]);
170    }
171
172    #[test]
173    fn test_kill_with_signal() {
174        let cmd = KillCommand::new("test-container").signal("SIGTERM");
175        let args = cmd.build_command_args();
176        assert_eq!(args, vec!["kill", "--signal", "SIGTERM", "test-container"]);
177    }
178
179    #[test]
180    fn test_kill_with_numeric_signal() {
181        let cmd = KillCommand::new("test-container").signal("9");
182        let args = cmd.build_command_args();
183        assert_eq!(args, vec!["kill", "--signal", "9", "test-container"]);
184    }
185
186    #[test]
187    fn test_kill_builder_chain() {
188        let cmd = KillCommand::new("container1")
189            .container("container2")
190            .container("container3")
191            .signal("SIGTERM");
192        let args = cmd.build_command_args();
193        assert_eq!(
194            args,
195            vec![
196                "kill",
197                "--signal",
198                "SIGTERM",
199                "container1",
200                "container2",
201                "container3"
202            ]
203        );
204    }
205}