docker_wrapper/command/
rm.rs

1//! Docker rm command implementation.
2//!
3//! This module provides the `docker rm` command for removing stopped containers.
4
5use super::{CommandExecutor, CommandOutput, DockerCommand};
6use crate::error::{Error, Result};
7use async_trait::async_trait;
8
9/// Docker rm command builder
10#[derive(Debug, Clone)]
11pub struct RmCommand {
12    /// Container names or IDs to remove
13    containers: Vec<String>,
14    /// Force removal of running containers
15    force: bool,
16    /// Remove anonymous volumes associated with the container
17    volumes: bool,
18    /// Remove the specified link
19    link: bool,
20    /// Command executor
21    pub executor: CommandExecutor,
22}
23
24impl RmCommand {
25    /// Create a new rm command for a single container
26    #[must_use]
27    pub fn new(container: impl Into<String>) -> Self {
28        Self {
29            containers: vec![container.into()],
30            force: false,
31            volumes: false,
32            link: false,
33            executor: CommandExecutor::new(),
34        }
35    }
36
37    /// Create a new rm command for multiple containers
38    #[must_use]
39    pub fn new_multiple(containers: Vec<impl Into<String>>) -> Self {
40        Self {
41            containers: containers.into_iter().map(Into::into).collect(),
42            force: false,
43            volumes: false,
44            link: false,
45            executor: CommandExecutor::new(),
46        }
47    }
48
49    /// Add another container to remove
50    #[must_use]
51    pub fn container(mut self, container: impl Into<String>) -> Self {
52        self.containers.push(container.into());
53        self
54    }
55
56    /// Force removal of running containers (uses SIGKILL)
57    #[must_use]
58    pub fn force(mut self) -> Self {
59        self.force = true;
60        self
61    }
62
63    /// Remove anonymous volumes associated with the container
64    #[must_use]
65    pub fn volumes(mut self) -> Self {
66        self.volumes = true;
67        self
68    }
69
70    /// Remove the specified link
71    #[must_use]
72    pub fn link(mut self) -> Self {
73        self.link = true;
74        self
75    }
76
77    /// Execute the rm command
78    ///
79    /// # Errors
80    /// Returns an error if:
81    /// - No containers are specified
82    /// - The Docker daemon is not running
83    /// - Any of the specified containers don't exist
84    /// - A container is running and force flag is not set
85    pub async fn run(&self) -> Result<RmResult> {
86        let output = self.execute().await?;
87
88        // Parse removed container IDs from output
89        let removed: Vec<String> = output
90            .stdout
91            .lines()
92            .filter(|line| !line.is_empty())
93            .map(String::from)
94            .collect();
95
96        Ok(RmResult { removed, output })
97    }
98}
99
100#[async_trait]
101impl DockerCommand for RmCommand {
102    type Output = CommandOutput;
103
104    fn get_executor(&self) -> &CommandExecutor {
105        &self.executor
106    }
107
108    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
109        &mut self.executor
110    }
111
112    fn build_command_args(&self) -> Vec<String> {
113        let mut args = vec!["rm".to_string()];
114
115        if self.force {
116            args.push("--force".to_string());
117        }
118
119        if self.volumes {
120            args.push("--volumes".to_string());
121        }
122
123        if self.link {
124            args.push("--link".to_string());
125        }
126
127        // Add container names/IDs
128        args.extend(self.containers.clone());
129        args.extend(self.executor.raw_args.clone());
130
131        args
132    }
133
134    async fn execute(&self) -> Result<Self::Output> {
135        if self.containers.is_empty() {
136            return Err(Error::invalid_config("No containers specified for removal"));
137        }
138
139        let args = self.build_command_args();
140        let command_name = args[0].clone();
141        let command_args = args[1..].to_vec();
142        self.executor
143            .execute_command(&command_name, command_args)
144            .await
145    }
146}
147
148/// Result from the rm command
149#[derive(Debug, Clone)]
150pub struct RmResult {
151    /// List of removed container IDs
152    pub removed: Vec<String>,
153    /// Raw command output
154    pub output: CommandOutput,
155}
156
157impl RmResult {
158    /// Check if all containers were removed successfully
159    #[must_use]
160    pub fn all_removed(&self) -> bool {
161        self.output.success
162    }
163
164    /// Get the number of containers removed
165    #[must_use]
166    pub fn count(&self) -> usize {
167        self.removed.len()
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn test_rm_single_container() {
177        let cmd = RmCommand::new("test-container");
178        let args = cmd.build_command_args();
179        assert_eq!(args, vec!["rm", "test-container"]);
180    }
181
182    #[test]
183    fn test_rm_multiple_containers() {
184        let cmd = RmCommand::new_multiple(vec!["container1", "container2", "container3"]);
185        let args = cmd.build_command_args();
186        assert_eq!(args, vec!["rm", "container1", "container2", "container3"]);
187    }
188
189    #[test]
190    fn test_rm_with_force() {
191        let cmd = RmCommand::new("test-container").force();
192        let args = cmd.build_command_args();
193        assert_eq!(args, vec!["rm", "--force", "test-container"]);
194    }
195
196    #[test]
197    fn test_rm_with_volumes() {
198        let cmd = RmCommand::new("test-container").volumes();
199        let args = cmd.build_command_args();
200        assert_eq!(args, vec!["rm", "--volumes", "test-container"]);
201    }
202
203    #[test]
204    fn test_rm_with_all_options() {
205        let cmd = RmCommand::new("test-container").force().volumes().link();
206        let args = cmd.build_command_args();
207        assert_eq!(
208            args,
209            vec!["rm", "--force", "--volumes", "--link", "test-container"]
210        );
211    }
212
213    #[test]
214    fn test_rm_builder_chain() {
215        let cmd = RmCommand::new("container1")
216            .container("container2")
217            .container("container3")
218            .force()
219            .volumes();
220        let args = cmd.build_command_args();
221        assert_eq!(
222            args,
223            vec![
224                "rm",
225                "--force",
226                "--volumes",
227                "container1",
228                "container2",
229                "container3"
230            ]
231        );
232    }
233}