docker_wrapper/command/
compose_down.rs1use super::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6use std::time::Duration;
7
8#[derive(Debug, Clone)]
10pub struct ComposeDownCommand {
11 pub executor: CommandExecutor,
13 pub config: ComposeConfig,
15 pub remove_images: Option<RemoveImages>,
17 pub volumes: bool,
19 pub remove_orphans: bool,
21 pub timeout: Option<Duration>,
23 pub services: Vec<String>,
25}
26
27#[derive(Debug, Clone, Copy)]
29pub enum RemoveImages {
30 All,
32 Local,
34}
35
36impl std::fmt::Display for RemoveImages {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 Self::All => write!(f, "all"),
40 Self::Local => write!(f, "local"),
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct ComposeDownResult {
48 pub stdout: String,
50 pub stderr: String,
52 pub success: bool,
54 pub removed_volumes: bool,
56 pub removed_images: bool,
58}
59
60impl ComposeDownCommand {
61 #[must_use]
63 pub fn new() -> Self {
64 Self {
65 executor: CommandExecutor::new(),
66 config: ComposeConfig::new(),
67 remove_images: None,
68 volumes: false,
69 remove_orphans: false,
70 timeout: None,
71 services: Vec::new(),
72 }
73 }
74
75 #[must_use]
77 pub fn remove_images(mut self, policy: RemoveImages) -> Self {
78 self.remove_images = Some(policy);
79 self
80 }
81
82 #[must_use]
84 pub fn volumes(mut self) -> Self {
85 self.volumes = true;
86 self
87 }
88
89 #[must_use]
91 pub fn remove_orphans(mut self) -> Self {
92 self.remove_orphans = true;
93 self
94 }
95
96 #[must_use]
98 pub fn timeout(mut self, timeout: Duration) -> Self {
99 self.timeout = Some(timeout);
100 self
101 }
102
103 #[must_use]
105 pub fn service(mut self, service: impl Into<String>) -> Self {
106 self.services.push(service.into());
107 self
108 }
109
110 #[must_use]
112 pub fn services<I, S>(mut self, services: I) -> Self
113 where
114 I: IntoIterator<Item = S>,
115 S: Into<String>,
116 {
117 self.services.extend(services.into_iter().map(Into::into));
118 self
119 }
120}
121
122impl Default for ComposeDownCommand {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128#[async_trait]
129impl DockerCommand for ComposeDownCommand {
130 type Output = ComposeDownResult;
131
132 fn get_executor(&self) -> &CommandExecutor {
133 &self.executor
134 }
135
136 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
137 &mut self.executor
138 }
139
140 fn build_command_args(&self) -> Vec<String> {
141 <Self as ComposeCommand>::build_command_args(self)
143 }
144
145 async fn execute(&self) -> Result<Self::Output> {
146 let args = <Self as ComposeCommand>::build_command_args(self);
147 let output = self.execute_command(args).await?;
148
149 Ok(ComposeDownResult {
150 stdout: output.stdout,
151 stderr: output.stderr,
152 success: output.success,
153 removed_volumes: self.volumes,
154 removed_images: self.remove_images.is_some(),
155 })
156 }
157}
158
159impl ComposeCommand for ComposeDownCommand {
160 fn get_config(&self) -> &ComposeConfig {
161 &self.config
162 }
163
164 fn get_config_mut(&mut self) -> &mut ComposeConfig {
165 &mut self.config
166 }
167
168 fn subcommand(&self) -> &'static str {
169 "down"
170 }
171
172 fn build_subcommand_args(&self) -> Vec<String> {
173 let mut args = Vec::new();
174
175 if let Some(ref remove) = self.remove_images {
176 args.push("--rmi".to_string());
177 args.push(remove.to_string());
178 }
179
180 if self.volumes {
181 args.push("--volumes".to_string());
182 }
183
184 if self.remove_orphans {
185 args.push("--remove-orphans".to_string());
186 }
187
188 if let Some(timeout) = self.timeout {
189 args.push("--timeout".to_string());
190 args.push(timeout.as_secs().to_string());
191 }
192
193 args.extend(self.services.clone());
195
196 args
197 }
198}
199
200impl ComposeDownResult {
201 #[must_use]
203 pub fn success(&self) -> bool {
204 self.success
205 }
206
207 #[must_use]
209 pub fn volumes_removed(&self) -> bool {
210 self.removed_volumes
211 }
212
213 #[must_use]
215 pub fn images_removed(&self) -> bool {
216 self.removed_images
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn test_compose_down_basic() {
226 let cmd = ComposeDownCommand::new();
227 let args = cmd.build_subcommand_args();
228 assert!(args.is_empty());
229
230 let full_args = ComposeCommand::build_command_args(&cmd);
231 assert_eq!(full_args[0], "compose");
232 assert!(full_args.contains(&"down".to_string()));
233 }
234
235 #[test]
236 fn test_compose_down_with_volumes() {
237 let cmd = ComposeDownCommand::new().volumes();
238 let args = cmd.build_subcommand_args();
239 assert_eq!(args, vec!["--volumes"]);
240 }
241
242 #[test]
243 fn test_compose_down_remove_images() {
244 let cmd = ComposeDownCommand::new().remove_images(RemoveImages::All);
245 let args = cmd.build_subcommand_args();
246 assert_eq!(args, vec!["--rmi", "all"]);
247 }
248
249 #[test]
250 fn test_compose_down_all_options() {
251 let cmd = ComposeDownCommand::new()
252 .remove_images(RemoveImages::Local)
253 .volumes()
254 .remove_orphans()
255 .timeout(Duration::from_secs(30))
256 .service("web")
257 .service("db");
258
259 let args = cmd.build_subcommand_args();
260 assert_eq!(
261 args,
262 vec![
263 "--rmi",
264 "local",
265 "--volumes",
266 "--remove-orphans",
267 "--timeout",
268 "30",
269 "web",
270 "db"
271 ]
272 );
273 }
274
275 #[test]
276 fn test_remove_images_display() {
277 assert_eq!(RemoveImages::All.to_string(), "all");
278 assert_eq!(RemoveImages::Local.to_string(), "local");
279 }
280
281 #[test]
282 fn test_compose_config_integration() {
283 let cmd = ComposeDownCommand::new()
284 .file("docker-compose.yml")
285 .project_name("my-project")
286 .volumes()
287 .remove_orphans();
288
289 let args = ComposeCommand::build_command_args(&cmd);
290 assert!(args.contains(&"--file".to_string()));
291 assert!(args.contains(&"docker-compose.yml".to_string()));
292 assert!(args.contains(&"--project-name".to_string()));
293 assert!(args.contains(&"my-project".to_string()));
294 assert!(args.contains(&"--volumes".to_string()));
295 assert!(args.contains(&"--remove-orphans".to_string()));
296 }
297}