lmrc_docker/containers/
mod.rs

1//! Container management operations.
2
3use crate::DockerClient;
4use crate::error::{DockerError, Result};
5use bollard::container::*;
6use bollard::exec::CreateExecOptions;
7use bollard::models::*;
8use futures_util::StreamExt;
9use std::collections::HashMap;
10use tracing::{debug, info};
11
12mod builder;
13pub use builder::*;
14
15/// Container operations manager.
16pub struct Containers<'a> {
17    client: &'a DockerClient,
18}
19
20impl<'a> Containers<'a> {
21    pub(crate) fn new(client: &'a DockerClient) -> Self {
22        Self { client }
23    }
24
25    /// Create a new container builder.
26    ///
27    /// # Example
28    ///
29    /// ```no_run
30    /// use lmrc_docker::DockerClient;
31    ///
32    /// #[tokio::main]
33    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
34    ///     let client = DockerClient::new()?;
35    ///
36    ///     let container = client.containers()
37    ///         .create("nginx:latest")
38    ///         .name("web-server")
39    ///         .port(8080, 80, "tcp")
40    ///         .env("ENV", "production")
41    ///         .build()
42    ///         .await?;
43    ///
44    ///     Ok(())
45    /// }
46    /// ```
47    pub fn create(&self, image: impl Into<String>) -> ContainerBuilder<'a> {
48        ContainerBuilder::new(self.client, image)
49    }
50
51    /// Get a reference to a specific container.
52    pub fn get(&self, name_or_id: impl Into<String>) -> ContainerRef<'a> {
53        ContainerRef::new(self.client, name_or_id.into())
54    }
55
56    /// List all containers.
57    ///
58    /// # Example
59    ///
60    /// ```no_run
61    /// use lmrc_docker::DockerClient;
62    ///
63    /// #[tokio::main]
64    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
65    ///     let client = DockerClient::new()?;
66    ///     let containers = client.containers().list(true).await?;
67    ///     for container in containers {
68    ///         println!("{:?}", container.names);
69    ///     }
70    ///     Ok(())
71    /// }
72    /// ```
73    pub async fn list(&self, all: bool) -> Result<Vec<ContainerSummary>> {
74        let mut filters = HashMap::new();
75        if all {
76            filters.insert("all", vec!["true"]);
77        }
78
79        let options: Option<ListContainersOptions<String>> = Some(ListContainersOptions {
80            all,
81            ..Default::default()
82        });
83
84        self.client
85            .docker
86            .list_containers(options)
87            .await
88            .map_err(|e| DockerError::Other(format!("Failed to list containers: {}", e)))
89    }
90
91    /// Prune stopped containers.
92    pub async fn prune(&self) -> Result<ContainerPruneResponse> {
93        info!("Pruning stopped containers...");
94        self.client
95            .docker
96            .prune_containers(Some(
97                bollard::query_parameters::PruneContainersOptions::default(),
98            ))
99            .await
100            .map_err(|e| DockerError::Other(format!("Failed to prune containers: {}", e)))
101    }
102}
103
104/// Reference to a specific container.
105pub struct ContainerRef<'a> {
106    client: &'a DockerClient,
107    id: String,
108}
109
110impl<'a> ContainerRef<'a> {
111    pub(crate) fn new(client: &'a DockerClient, id: String) -> Self {
112        Self { client, id }
113    }
114
115    /// Get the container ID.
116    pub fn id(&self) -> &str {
117        &self.id
118    }
119
120    /// Start the container.
121    ///
122    /// # Example
123    ///
124    /// ```no_run
125    /// use lmrc_docker::DockerClient;
126    ///
127    /// #[tokio::main]
128    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
129    ///     let client = DockerClient::new()?;
130    ///     client.containers().get("my-container").start().await?;
131    ///     Ok(())
132    /// }
133    /// ```
134    pub async fn start(&self) -> Result<()> {
135        info!("Starting container: {}", self.id);
136        self.client
137            .docker
138            .start_container(
139                &self.id,
140                Some(bollard::query_parameters::StartContainerOptions::default()),
141            )
142            .await
143            .map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to start: {}", e)))
144    }
145
146    /// Stop the container.
147    ///
148    /// # Arguments
149    ///
150    /// * `timeout` - Optional timeout in seconds before killing the container
151    pub async fn stop(&self, timeout: Option<i64>) -> Result<()> {
152        info!("Stopping container: {}", self.id);
153        let options = timeout.map(|t| StopContainerOptions { t });
154        self.client
155            .docker
156            .stop_container(&self.id, options)
157            .await
158            .map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to stop: {}", e)))
159    }
160
161    /// Restart the container.
162    pub async fn restart(&self, timeout: Option<i64>) -> Result<()> {
163        info!("Restarting container: {}", self.id);
164        let options = timeout.map(|t| RestartContainerOptions { t: t as isize });
165        self.client
166            .docker
167            .restart_container(&self.id, options)
168            .await
169            .map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to restart: {}", e)))
170    }
171
172    /// Kill the container with optional signal.
173    pub async fn kill(&self, signal: Option<&str>) -> Result<()> {
174        info!("Killing container: {}", self.id);
175        let options = signal.map(|s| KillContainerOptions { signal: s });
176        self.client
177            .docker
178            .kill_container(&self.id, options)
179            .await
180            .map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to kill: {}", e)))
181    }
182
183    /// Pause the container.
184    pub async fn pause(&self) -> Result<()> {
185        info!("Pausing container: {}", self.id);
186        self.client
187            .docker
188            .pause_container(&self.id)
189            .await
190            .map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to pause: {}", e)))
191    }
192
193    /// Unpause the container.
194    pub async fn unpause(&self) -> Result<()> {
195        info!("Unpausing container: {}", self.id);
196        self.client
197            .docker
198            .unpause_container(&self.id)
199            .await
200            .map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to unpause: {}", e)))
201    }
202
203    /// Remove the container.
204    ///
205    /// # Arguments
206    ///
207    /// * `force` - Force remove even if running
208    /// * `remove_volumes` - Remove associated volumes
209    pub async fn remove(&self, force: bool, remove_volumes: bool) -> Result<()> {
210        info!("Removing container: {}", self.id);
211        let options = Some(RemoveContainerOptions {
212            force,
213            v: remove_volumes,
214            ..Default::default()
215        });
216        self.client
217            .docker
218            .remove_container(&self.id, options)
219            .await
220            .map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to remove: {}", e)))
221    }
222
223    /// Inspect the container to get detailed information.
224    pub async fn inspect(&self) -> Result<ContainerInspectResponse> {
225        debug!("Inspecting container: {}", self.id);
226        self.client
227            .docker
228            .inspect_container(
229                &self.id,
230                Some(bollard::query_parameters::InspectContainerOptions::default()),
231            )
232            .await
233            .map_err(|e| DockerError::ContainerNotFound(format!("{}: {}", self.id, e)))
234    }
235
236    /// Get container logs.
237    ///
238    /// # Arguments
239    ///
240    /// * `follow` - Follow log output
241    /// * `stdout` - Show stdout
242    /// * `stderr` - Show stderr
243    /// * `tail` - Number of lines to show from end (default: all)
244    pub async fn logs(
245        &self,
246        follow: bool,
247        stdout: bool,
248        stderr: bool,
249        tail: Option<&str>,
250    ) -> Result<Vec<String>> {
251        debug!("Getting logs for container: {}", self.id);
252        let options: Option<LogsOptions<String>> = Some(LogsOptions {
253            follow,
254            stdout,
255            stderr,
256            tail: tail.unwrap_or("all").to_string(),
257            ..Default::default()
258        });
259
260        let mut stream = self.client.docker.logs(&self.id, options);
261        let mut logs = Vec::new();
262
263        while let Some(chunk) = stream.next().await {
264            match chunk {
265                Ok(output) => {
266                    logs.push(output.to_string());
267                }
268                Err(e) => {
269                    return Err(DockerError::ContainerOperationFailed(format!(
270                        "Failed to get logs: {}",
271                        e
272                    )));
273                }
274            }
275        }
276
277        Ok(logs)
278    }
279
280    // Note: stats() method removed - use bollard directly via client.inner() for advanced stats
281
282    /// Rename the container.
283    pub async fn rename(&self, new_name: impl Into<String>) -> Result<()> {
284        let new_name = new_name.into();
285        info!("Renaming container {} to {}", self.id, new_name);
286        let options = RenameContainerOptions { name: new_name };
287        self.client
288            .docker
289            .rename_container(&self.id, options)
290            .await
291            .map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to rename: {}", e)))
292    }
293
294    /// Execute a command in the container and return the exec ID.
295    pub async fn exec(&self, cmd: Vec<String>, tty: bool) -> Result<String> {
296        debug!("Executing command in container {}: {:?}", self.id, cmd);
297        let config = CreateExecOptions {
298            cmd: Some(cmd),
299            attach_stdout: Some(true),
300            attach_stderr: Some(true),
301            tty: Some(tty),
302            ..Default::default()
303        };
304
305        let response = self
306            .client
307            .docker
308            .create_exec(&self.id, config)
309            .await
310            .map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to exec: {}", e)))?;
311
312        Ok(response.id)
313    }
314
315    // Note: top() method removed - use bollard directly via client.inner() for process listing
316}
317
318impl DockerClient {
319    /// Access container operations.
320    ///
321    /// # Example
322    ///
323    /// ```no_run
324    /// use lmrc_docker::DockerClient;
325    ///
326    /// #[tokio::main]
327    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
328    ///     let client = DockerClient::new()?;
329    ///     let containers = client.containers().list(true).await?;
330    ///     Ok(())
331    /// }
332    /// ```
333    pub fn containers(&self) -> Containers<'_> {
334        Containers::new(self)
335    }
336}