Skip to main content

ironflow_ops_docker/containers/
state.rs

1//! Container state operations: pause, unpause, rename, top.
2
3use async_trait::async_trait;
4use bollard::Docker;
5use bollard::query_parameters::{RenameContainerOptions, TopOptions};
6use ironflow_core::error::OperationError;
7use ironflow_core::operation::{Operation, OperationContext, TypedOperation};
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11use crate::containers::DockerRef;
12use crate::helpers::{docker_error, to_value};
13
14// ---------------------------------------------------------------------------
15// ContainerPause
16// ---------------------------------------------------------------------------
17
18/// Output of a container pause.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ContainerPauseOutput {
21    /// The container ID or name.
22    pub container: String,
23}
24
25/// Pause a running container.
26///
27/// # Examples
28///
29/// ```no_run
30/// use ironflow_ops_docker::containers::ContainerPause;
31/// use ironflow_ops_docker::DockerClient;
32/// use ironflow_core::operation::Operation;
33///
34/// let client = DockerClient::connect_local().unwrap();
35/// let op = ContainerPause::new(&client, "my-container");
36/// assert_eq!(op.kind(), "docker");
37/// ```
38pub struct ContainerPause {
39    docker: Docker,
40    container: String,
41}
42
43impl ContainerPause {
44    /// Create a new container-pause operation.
45    pub fn new(client: impl Into<DockerRef>, container: impl Into<String>) -> Self {
46        Self {
47            docker: client.into().0,
48            container: container.into(),
49        }
50    }
51
52    /// Execute and return a typed result.
53    ///
54    /// # Errors
55    ///
56    /// Returns [`OperationError::External`] if the container does not exist.
57    pub async fn run(
58        &self,
59        _ctx: &OperationContext,
60    ) -> Result<ContainerPauseOutput, OperationError> {
61        self.docker
62            .pause_container(&self.container)
63            .await
64            .map_err(docker_error)?;
65        Ok(ContainerPauseOutput {
66            container: self.container.clone(),
67        })
68    }
69}
70
71#[async_trait]
72impl Operation for ContainerPause {
73    fn kind(&self) -> &str {
74        "docker"
75    }
76
77    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
78        to_value(&self.run(ctx).await?)
79    }
80
81    fn input(&self) -> Option<Value> {
82        Some(serde_json::json!({
83            "operation": "container_pause",
84            "container": self.container,
85        }))
86    }
87}
88
89impl TypedOperation for ContainerPause {
90    type Output = ContainerPauseOutput;
91}
92
93// ---------------------------------------------------------------------------
94// ContainerUnpause
95// ---------------------------------------------------------------------------
96
97/// Output of a container unpause.
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct ContainerUnpauseOutput {
100    /// The container ID or name.
101    pub container: String,
102}
103
104/// Unpause a paused container.
105///
106/// # Examples
107///
108/// ```no_run
109/// use ironflow_ops_docker::containers::ContainerUnpause;
110/// use ironflow_ops_docker::DockerClient;
111/// use ironflow_core::operation::Operation;
112///
113/// let client = DockerClient::connect_local().unwrap();
114/// let op = ContainerUnpause::new(&client, "my-container");
115/// assert_eq!(op.kind(), "docker");
116/// ```
117pub struct ContainerUnpause {
118    docker: Docker,
119    container: String,
120}
121
122impl ContainerUnpause {
123    /// Create a new container-unpause operation.
124    pub fn new(client: impl Into<DockerRef>, container: impl Into<String>) -> Self {
125        Self {
126            docker: client.into().0,
127            container: container.into(),
128        }
129    }
130
131    /// Execute and return a typed result.
132    ///
133    /// # Errors
134    ///
135    /// Returns [`OperationError::External`] if the container does not exist.
136    pub async fn run(
137        &self,
138        _ctx: &OperationContext,
139    ) -> Result<ContainerUnpauseOutput, OperationError> {
140        self.docker
141            .unpause_container(&self.container)
142            .await
143            .map_err(docker_error)?;
144        Ok(ContainerUnpauseOutput {
145            container: self.container.clone(),
146        })
147    }
148}
149
150#[async_trait]
151impl Operation for ContainerUnpause {
152    fn kind(&self) -> &str {
153        "docker"
154    }
155
156    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
157        to_value(&self.run(ctx).await?)
158    }
159
160    fn input(&self) -> Option<Value> {
161        Some(serde_json::json!({
162            "operation": "container_unpause",
163            "container": self.container,
164        }))
165    }
166}
167
168impl TypedOperation for ContainerUnpause {
169    type Output = ContainerUnpauseOutput;
170}
171
172// ---------------------------------------------------------------------------
173// ContainerRename
174// ---------------------------------------------------------------------------
175
176/// Output of a container rename.
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct ContainerRenameOutput {
179    /// The old container name.
180    pub old_name: String,
181    /// The new container name.
182    pub new_name: String,
183}
184
185/// Rename a container.
186///
187/// # Examples
188///
189/// ```no_run
190/// use ironflow_ops_docker::containers::ContainerRename;
191/// use ironflow_ops_docker::DockerClient;
192/// use ironflow_core::operation::Operation;
193///
194/// let client = DockerClient::connect_local().unwrap();
195/// let op = ContainerRename::new(&client, "old-name", "new-name");
196/// assert_eq!(op.kind(), "docker");
197/// ```
198pub struct ContainerRename {
199    docker: Docker,
200    container: String,
201    new_name: String,
202}
203
204impl ContainerRename {
205    /// Create a new container-rename operation.
206    pub fn new(
207        client: impl Into<DockerRef>,
208        container: impl Into<String>,
209        new_name: impl Into<String>,
210    ) -> Self {
211        Self {
212            docker: client.into().0,
213            container: container.into(),
214            new_name: new_name.into(),
215        }
216    }
217
218    /// Execute and return a typed result.
219    ///
220    /// # Errors
221    ///
222    /// Returns [`OperationError::External`] if the container does not exist.
223    pub async fn run(
224        &self,
225        _ctx: &OperationContext,
226    ) -> Result<ContainerRenameOutput, OperationError> {
227        let options = RenameContainerOptions {
228            name: self.new_name.clone(),
229        };
230        self.docker
231            .rename_container(&self.container, options)
232            .await
233            .map_err(docker_error)?;
234        Ok(ContainerRenameOutput {
235            old_name: self.container.clone(),
236            new_name: self.new_name.clone(),
237        })
238    }
239}
240
241#[async_trait]
242impl Operation for ContainerRename {
243    fn kind(&self) -> &str {
244        "docker"
245    }
246
247    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
248        to_value(&self.run(ctx).await?)
249    }
250
251    fn input(&self) -> Option<Value> {
252        Some(serde_json::json!({
253            "operation": "container_rename",
254            "container": self.container,
255            "new_name": self.new_name,
256        }))
257    }
258}
259
260impl TypedOperation for ContainerRename {
261    type Output = ContainerRenameOutput;
262}
263
264// ---------------------------------------------------------------------------
265// ContainerTop
266// ---------------------------------------------------------------------------
267
268/// Output of a container top (process list).
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct ContainerTopOutput {
271    /// Column titles.
272    pub titles: Vec<String>,
273    /// Process rows.
274    pub processes: Vec<Vec<String>>,
275}
276
277/// List processes running inside a container.
278///
279/// # Examples
280///
281/// ```no_run
282/// use ironflow_ops_docker::containers::ContainerTop;
283/// use ironflow_ops_docker::DockerClient;
284/// use ironflow_core::operation::Operation;
285///
286/// let client = DockerClient::connect_local().unwrap();
287/// let op = ContainerTop::new(&client, "my-container");
288/// assert_eq!(op.kind(), "docker");
289/// ```
290pub struct ContainerTop {
291    docker: Docker,
292    container: String,
293    ps_args: Option<String>,
294}
295
296impl ContainerTop {
297    /// Create a new container-top operation.
298    pub fn new(client: impl Into<DockerRef>, container: impl Into<String>) -> Self {
299        Self {
300            docker: client.into().0,
301            container: container.into(),
302            ps_args: None,
303        }
304    }
305
306    /// Set custom ps arguments.
307    pub fn ps_args(mut self, args: impl Into<String>) -> Self {
308        self.ps_args = Some(args.into());
309        self
310    }
311
312    /// Execute and return a typed result.
313    ///
314    /// # Errors
315    ///
316    /// Returns [`OperationError::External`] if the container does not exist or
317    /// is not running.
318    pub async fn run(&self, _ctx: &OperationContext) -> Result<ContainerTopOutput, OperationError> {
319        let options = self.ps_args.as_ref().map(|args| TopOptions {
320            ps_args: args.to_string(),
321        });
322        let response = self
323            .docker
324            .top_processes(&self.container, options)
325            .await
326            .map_err(docker_error)?;
327        Ok(ContainerTopOutput {
328            titles: response.titles.unwrap_or_default(),
329            processes: response.processes.unwrap_or_default(),
330        })
331    }
332}
333
334#[async_trait]
335impl Operation for ContainerTop {
336    fn kind(&self) -> &str {
337        "docker"
338    }
339
340    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
341        to_value(&self.run(ctx).await?)
342    }
343
344    fn input(&self) -> Option<Value> {
345        Some(serde_json::json!({
346            "operation": "container_top",
347            "container": self.container,
348        }))
349    }
350}
351
352impl TypedOperation for ContainerTop {
353    type Output = ContainerTopOutput;
354}