docker_wrapper/command/
kill.rs1use super::{CommandExecutor, CommandOutput, DockerCommand};
48use crate::error::{Error, Result};
49use async_trait::async_trait;
50
51#[derive(Debug, Clone)]
53pub struct KillCommand {
54 containers: Vec<String>,
56 signal: Option<String>,
58 pub executor: CommandExecutor,
60}
61
62impl KillCommand {
63 #[must_use]
65 pub fn new(container: impl Into<String>) -> Self {
66 Self {
67 containers: vec![container.into()],
68 signal: None,
69 executor: CommandExecutor::new(),
70 }
71 }
72
73 #[must_use]
75 pub fn new_multiple(containers: Vec<impl Into<String>>) -> Self {
76 Self {
77 containers: containers.into_iter().map(Into::into).collect(),
78 signal: None,
79 executor: CommandExecutor::new(),
80 }
81 }
82
83 #[must_use]
85 pub fn container(mut self, container: impl Into<String>) -> Self {
86 self.containers.push(container.into());
87 self
88 }
89
90 #[must_use]
92 pub fn signal(mut self, signal: impl Into<String>) -> Self {
93 self.signal = Some(signal.into());
94 self
95 }
96
97 pub async fn run(&self) -> Result<KillResult> {
106 let output = self.execute().await?;
107
108 let killed: Vec<String> = output
110 .stdout
111 .lines()
112 .filter(|line| !line.is_empty())
113 .map(String::from)
114 .collect();
115
116 Ok(KillResult {
117 killed,
118 signal: self.signal.clone(),
119 output,
120 })
121 }
122}
123
124#[async_trait]
125impl DockerCommand for KillCommand {
126 type Output = CommandOutput;
127
128 fn build_command_args(&self) -> Vec<String> {
129 let mut args = vec!["kill".to_string()];
130
131 if let Some(ref sig) = self.signal {
132 args.push("--signal".to_string());
133 args.push(sig.clone());
134 }
135
136 args.extend(self.containers.clone());
138
139 args.extend(self.executor.raw_args.clone());
140 args
141 }
142
143 fn get_executor(&self) -> &CommandExecutor {
144 &self.executor
145 }
146
147 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
148 &mut self.executor
149 }
150
151 async fn execute(&self) -> Result<Self::Output> {
152 if self.containers.is_empty() {
153 return Err(Error::invalid_config("No containers specified for kill"));
154 }
155
156 let args = self.build_command_args();
157 let command_name = args[0].clone();
158 let command_args = args[1..].to_vec();
159 self.executor
160 .execute_command(&command_name, command_args)
161 .await
162 }
163}
164
165#[derive(Debug, Clone)]
167pub struct KillResult {
168 pub killed: Vec<String>,
170 pub signal: Option<String>,
172 pub output: CommandOutput,
174}
175
176impl KillResult {
177 #[must_use]
179 pub fn all_killed(&self) -> bool {
180 self.output.success
181 }
182
183 #[must_use]
185 pub fn count(&self) -> usize {
186 self.killed.len()
187 }
188
189 #[must_use]
191 pub fn signal_sent(&self) -> &str {
192 self.signal.as_deref().unwrap_or("SIGKILL")
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_kill_single_container() {
202 let cmd = KillCommand::new("test-container");
203 let args = cmd.build_command_args();
204 assert_eq!(args, vec!["kill", "test-container"]);
205 }
206
207 #[test]
208 fn test_kill_multiple_containers() {
209 let cmd = KillCommand::new_multiple(vec!["container1", "container2", "container3"]);
210 let args = cmd.build_command_args();
211 assert_eq!(args, vec!["kill", "container1", "container2", "container3"]);
212 }
213
214 #[test]
215 fn test_kill_with_signal() {
216 let cmd = KillCommand::new("test-container").signal("SIGTERM");
217 let args = cmd.build_command_args();
218 assert_eq!(args, vec!["kill", "--signal", "SIGTERM", "test-container"]);
219 }
220
221 #[test]
222 fn test_kill_with_numeric_signal() {
223 let cmd = KillCommand::new("test-container").signal("9");
224 let args = cmd.build_command_args();
225 assert_eq!(args, vec!["kill", "--signal", "9", "test-container"]);
226 }
227
228 #[test]
229 fn test_kill_builder_chain() {
230 let cmd = KillCommand::new("container1")
231 .container("container2")
232 .container("container3")
233 .signal("SIGTERM");
234 let args = cmd.build_command_args();
235 assert_eq!(
236 args,
237 vec![
238 "kill",
239 "--signal",
240 "SIGTERM",
241 "container1",
242 "container2",
243 "container3"
244 ]
245 );
246 }
247}