passivized_docker_engine_client/client/
container.rs

1use hyper::StatusCode;
2
3use crate::client::container_files::DecContainerFiles;
4use crate::client::shared::parse_container_log;
5use crate::DockerEngineClient;
6use crate::errors::DecUseError;
7use crate::requests::{CreateExecRequest, InspectContainerArgs, LogsArgs, RemoveContainerArgs, WaitCondition};
8use crate::responses::{CreateExecResponse, InspectContainerResponse, TopResponse, WaitResponse};
9use crate::model::{StreamLine, TsStreamLine};
10
11pub struct DecContainer<'a> {
12    pub(super) client: &'a DockerEngineClient,
13    pub(super) container_id: String
14}
15
16impl <'a> DecContainer<'a> {
17
18    /// Create a command to run within a container, but don't start or execute the command.
19    pub async fn create_exec(&self, request: CreateExecRequest) -> Result<CreateExecResponse, DecUseError> {
20        let uri = self.client.url.containers().create_exec(&self.container_id);
21        let response = self.client.http.post_json(uri, &request)?.execute().await?;
22
23        response
24            .assert_item_status(StatusCode::CREATED)?
25            .parse()
26    }
27
28    /// Work with files inside a container.
29    pub fn files(&'_ self) -> DecContainerFiles<'_> {
30        DecContainerFiles {
31            client: self.client,
32            container_id: &self.container_id
33        }
34    }
35
36    /// Get the console output (stdout and/or stderr) of a container.
37    ///
38    /// # Example
39    ///
40    /// ```rust
41    /// use passivized_docker_engine_client::DockerEngineClient;
42    /// use passivized_docker_engine_client::errors::DecError;
43    ///
44    /// async fn example() -> Result<(), DecError> {
45    ///     let dec = DockerEngineClient::new()?;
46    ///     let log = dec.container("example").logs().await?;
47    ///
48    ///     println!("Container log:");
49    ///     for line in log {
50    ///         println!("{}", line.text);
51    ///     }
52    ///
53    ///     Ok(())
54    /// }
55    /// ```
56    pub async fn logs(&self) -> Result<Vec<StreamLine>, DecUseError> {
57        self.logs_with(LogsArgs::default()).await
58    }
59
60    pub async fn logs_timestamped(&self) -> Result<Vec<Result<TsStreamLine, String>>, DecUseError> {
61        let lines = self.logs_with(LogsArgs::default().timestamps()).await?;
62
63        let results = lines
64            .iter()
65            .map(TsStreamLine::try_from)
66            .collect();
67
68        Ok(results)
69    }
70
71    async fn logs_with(&self, args: LogsArgs) -> Result<Vec<StreamLine>, DecUseError> {
72        let uri = self.client.url.containers().logs(&self.container_id, args)?;
73        let response = self.client.http.get(uri)?.execute().await?;
74
75        response
76            .assert_item_status(StatusCode::OK)?
77            .parse_with(parse_container_log)
78    }
79
80    /// Inspect a container.
81    ///
82    /// # Example
83    ///
84    /// ```rust
85    /// use passivized_docker_engine_client::DockerEngineClient;
86    /// use passivized_docker_engine_client::errors::DecError;
87    ///
88    /// async fn example() -> Result<(), DecError> {
89    ///     let dec = DockerEngineClient::new()?;
90    ///     let container = dec.container("example").inspect().await?;
91    ///
92    ///     println!("Container status: {}", container.state.status);
93    ///
94    ///     Ok(())
95    /// }
96    /// ```
97    pub async fn inspect(&self) -> Result<InspectContainerResponse, DecUseError> {
98        self.inspect_with(InspectContainerArgs::default()).await
99    }
100
101    /// Inspect a container, with additional request arguments.
102    ///
103    /// # Example
104    ///
105    /// ```rust
106    /// use passivized_docker_engine_client::DockerEngineClient;
107    /// use passivized_docker_engine_client::requests::InspectContainerArgs;
108    /// use passivized_docker_engine_client::errors::DecError;
109    ///
110    /// async fn example() -> Result<(), DecError> {
111    ///     let dec = DockerEngineClient::new()?;
112    ///     let container = dec.container("example")
113    ///         .inspect_with(InspectContainerArgs::default().size(true))
114    ///         .await?;
115    ///
116    ///     let container_size = container.size_root_fs
117    ///         .map(|size| size.to_string())
118    ///         .unwrap_or("unknown".into());
119    ///
120    ///     println!("Container status: {}", container.state.status);
121    ///     println!("Container root file system size: {}", container_size);
122    ///
123    ///     Ok(())
124    /// }
125    /// ```
126    pub async fn inspect_with(&self, args: InspectContainerArgs) -> Result<InspectContainerResponse, DecUseError> {
127        let uri = self.client.url.containers().inspect(&self.container_id, args.size.unwrap_or_default());
128        let response = self.client.http.get(uri)?.execute().await?;
129
130        response
131            .assert_item_status(StatusCode::OK)?
132            .parse()
133    }
134
135    /// Abort a running container, using the default process signal (as determined
136    /// by the Docker Engine and/or the container's configuration or the configuration
137    /// of the image the container is based on).
138    pub async fn kill<S: Into<String>>(&self) -> Result<(), DecUseError> {
139        self.kill_opt(None).await
140    }
141
142    /// Send a signal to a container.
143    ///
144    /// Multiple signals are supported, not just SIGKILL.
145    ///
146    /// # Arguments
147    /// * `signal` - name of signal to send
148    ///
149    /// # Example
150    ///
151    /// ```rust
152    /// use passivized_docker_engine_client::DockerEngineClient;
153    /// use passivized_docker_engine_client::errors::DecError;
154    ///
155    /// async fn example() -> Result<(), DecError> {
156    ///     let dec = DockerEngineClient::new()?;
157    ///
158    ///     dec.container("example").kill_with("SIGTERM").await?;
159    ///     Ok(())
160    /// }
161    /// ```
162    pub async fn kill_with<S: Into<String>>(&self, signal: S) -> Result<(), DecUseError> {
163        self.kill_opt(Some(signal.into())).await
164    }
165
166    async fn kill_opt(&self, signal: Option<String>) -> Result<(), DecUseError> {
167        let uri = self.client.url.containers().kill(&self.container_id, signal)?;
168        let response = self.client.http.post(uri)?.execute().await?;
169
170        response
171            .assert_unit_status(StatusCode::NO_CONTENT)
172    }
173
174    /// Pause a running container.
175    ///
176    /// # Example
177    ///
178    /// ```rust
179    /// use passivized_docker_engine_client::DockerEngineClient;
180    /// use passivized_docker_engine_client::errors::DecError;
181    ///
182    /// async fn example() -> Result<(), DecError> {
183    ///     let dec = DockerEngineClient::new()?;
184    ///
185    ///     dec.container("example").pause().await?;
186    ///     Ok(())
187    /// }
188    /// ```
189    #[cfg(not(windows))]  // Docker for Windows does not support pausing containers.
190    pub async fn pause(&self) -> Result<(), DecUseError> {
191        let uri = self.client.url.containers().pause(&self.container_id);
192        let response = self.client.http.post(uri)?.execute().await?;
193
194        response
195            .assert_unit_status(StatusCode::NO_CONTENT)
196    }
197
198    /// Delete a stopped container.
199    ///
200    /// Remove requests are NOT idempotent.
201    pub async fn remove(&self) -> Result<(), DecUseError> {
202        self.remove_with(RemoveContainerArgs::default()).await
203    }
204
205    /// Delete a container.
206    ///
207    /// Remove requests are NOT idempotent; attempting to remove a removed container will return an error.
208    ///
209    /// # Arguments
210    /// * `args` are additional request arguments for the removal.
211    ///
212    /// # Example
213    ///
214    /// ```rust
215    /// use passivized_docker_engine_client::DockerEngineClient;
216    /// use passivized_docker_engine_client::errors::DecError;
217    /// use passivized_docker_engine_client::requests::RemoveContainerArgs;
218    ///
219    /// async fn example() -> Result<(), DecError> {
220    ///     let dec = DockerEngineClient::new()?;
221    ///
222    ///     let args = RemoveContainerArgs::default()
223    ///         .remove_volumes(true);
224    ///
225    ///     dec.container("example").remove_with(args).await?;
226    ///
227    ///     Ok(())
228    /// }
229    /// ```
230    pub async fn remove_with(&self, args: RemoveContainerArgs) -> Result<(), DecUseError> {
231        let uri = self.client.url.containers().remove(&self.container_id, args)?;
232        let response = self.client.http.delete(uri)?.execute().await?;
233
234        response
235            .assert_unit_status(StatusCode::NO_CONTENT)
236    }
237
238    /// Rename an existing container.
239    ///
240    /// # Arguments
241    /// * `new_name` - New name for the container.
242    ///
243    /// # Example
244    ///
245    /// ```rust
246    /// use passivized_docker_engine_client::DockerEngineClient;
247    /// use passivized_docker_engine_client::errors::DecError;
248    ///
249    /// async fn example() -> Result<(), DecError> {
250    ///     let dec = DockerEngineClient::new()?;
251    ///
252    ///     dec.container("example").rename("example2").await?;
253    ///
254    ///     Ok(())
255    /// }
256    /// ```
257    pub async fn rename<NN: Into<String>>(&self, new_name: NN) -> Result<(), DecUseError> {
258        let uri = self.client.url.containers().rename(&self.container_id, new_name.into());
259        let response = self.client.http.post(uri)?.execute().await?;
260
261        response
262            .assert_unit_status(StatusCode::NO_CONTENT)
263    }
264
265    /// Start an existing container.
266    ///
267    /// This is idempotent.
268    ///
269    /// # Example
270    ///
271    /// ```rust
272    /// use passivized_docker_engine_client::DockerEngineClient;
273    /// use passivized_docker_engine_client::errors::DecError;
274    ///
275    /// async fn example() -> Result<(), DecError> {
276    ///     let dec = DockerEngineClient::new()?;
277    ///
278    ///     dec.container("example").start().await?;
279    ///
280    ///     Ok(())
281    /// }
282    /// ```
283    pub async fn start(&self) -> Result<(), DecUseError> {
284        let uri = self.client.url.containers().start(&self.container_id);
285        let response = self.client.http.post(uri)?.execute().await?;
286
287        response
288            // See https://docs.docker.com/engine/api/v1.41/#tag/Container/operation/ContainerStart
289            .assert_unit_status_in(&[
290                // No error
291                StatusCode::NO_CONTENT,
292                // Already started
293                StatusCode::NOT_MODIFIED
294            ])
295    }
296
297    /// Stop a running container.
298    ///
299    /// This is idempotent.
300    ///
301    /// # Example
302    ///
303    /// ```rust
304    /// use passivized_docker_engine_client::DockerEngineClient;
305    /// use passivized_docker_engine_client::errors::DecError;
306    ///
307    /// async fn example() -> Result<(), DecError> {
308    ///     let dec = DockerEngineClient::new()?;
309    ///
310    ///     dec.container("example").stop().await?;
311    ///
312    ///     Ok(())
313    /// }
314    /// ```
315    pub async fn stop(&self) -> Result<(), DecUseError> {
316        let uri = self.client.url.containers().stop(&self.container_id);
317        let response = self.client.http.post(uri)?.execute().await?;
318
319        response
320            // See https://docs.docker.com/engine/api/v1.41/#tag/Container/operation/ContainerStop
321            .assert_unit_status_in(&[
322                // No error
323                StatusCode::NO_CONTENT,
324                // Already stopped
325                StatusCode::NOT_MODIFIED
326            ])
327    }
328
329    /// Get a list of processes running inside the container.
330    pub async fn top(&self) -> Result<TopResponse, DecUseError> {
331        self.top_opt(None).await
332    }
333
334    /// Get a list of processes running inside the container, customizing the output.
335    pub async fn top_with(&self, ps_args: String) -> Result<TopResponse, DecUseError> {
336        self.top_opt(Some(ps_args)).await
337    }
338
339    async fn top_opt(&self, ps_args: Option<String>) -> Result<TopResponse, DecUseError> {
340        let uri = self.client.url.containers().top(&self.container_id, ps_args)?;
341        let response = self.client.http.get(uri)?.execute().await?;
342
343        response
344            .assert_item_status(StatusCode::OK)?
345            .parse()
346    }
347
348    /// Unpause a paused container.
349    ///
350    /// # Example
351    ///
352    /// ```rust
353    /// use passivized_docker_engine_client::DockerEngineClient;
354    /// use passivized_docker_engine_client::errors::DecError;
355    ///
356    /// async fn example() -> Result<(), DecError> {
357    ///     let dec = DockerEngineClient::new()?;
358    ///
359    ///     dec.container("example").unpause().await?;
360    ///
361    ///     Ok(())
362    /// }
363    /// ```
364    #[cfg(not(windows))]  // Docker for Windows does not support pausing containers.
365    pub async fn unpause(&self) -> Result<(), DecUseError> {
366        let uri = self.client.url.containers().unpause(&self.container_id);
367        let response = self.client.http.post(uri)?.execute().await?;
368
369        response
370            .assert_unit_status(StatusCode::NO_CONTENT)
371    }
372
373    /// Wait for a container to reach a specific state.
374    ///
375    /// For process stop states, blocks until the container stops, then returns the exit code.
376    ///
377    /// # Arguments
378    /// `condition` - Desired condition to wait for.
379    ///
380    /// # Example
381    ///
382    /// ```rust
383    /// use passivized_docker_engine_client::DockerEngineClient;
384    /// use passivized_docker_engine_client::errors::DecError;
385    /// use passivized_docker_engine_client::requests::WaitCondition;
386    ///
387    /// async fn example() -> Result<(), DecError> {
388    ///     let dec = DockerEngineClient::new()?;
389    ///
390    ///     dec.container("example").wait(WaitCondition::NotRunning).await?;
391    ///
392    ///     Ok(())
393    /// }
394    /// ```
395    pub async fn wait(&self, condition: WaitCondition) -> Result<WaitResponse, DecUseError> {
396        let uri = self.client.url.containers().wait(&self.container_id, condition);
397        let response = self.client.http.post(uri)?.execute().await?;
398
399        response
400            .assert_item_status(StatusCode::OK)?
401            .parse()
402    }
403
404}