Skip to main content

ironflow_ops_docker/networks/
manage.rs

1//! Network management: create, inspect, list, remove, prune.
2
3use std::collections::HashMap;
4
5use async_trait::async_trait;
6use bollard::Docker;
7use bollard::models::NetworkCreateRequest;
8use bollard::query_parameters::{InspectNetworkOptions, ListNetworksOptions, PruneNetworksOptions};
9use ironflow_core::error::OperationError;
10use ironflow_core::operation::{Operation, OperationContext, TypedOperation};
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13
14use crate::containers::DockerRef;
15use crate::helpers::{docker_error, to_value};
16
17// ---------------------------------------------------------------------------
18// NetworkCreate
19// ---------------------------------------------------------------------------
20
21/// Output of a network creation.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct NetworkCreateOutput {
24    /// The created network ID.
25    pub id: String,
26}
27
28/// Create a network.
29///
30/// # Examples
31///
32/// ```no_run
33/// use ironflow_ops_docker::networks::NetworkCreate;
34/// use ironflow_ops_docker::DockerClient;
35/// use ironflow_core::operation::Operation;
36///
37/// let client = DockerClient::connect_local().unwrap();
38/// let op = NetworkCreate::new(&client, "my-network");
39/// assert_eq!(op.kind(), "docker");
40/// ```
41pub struct NetworkCreate {
42    docker: Docker,
43    name: String,
44    driver: Option<String>,
45}
46
47impl NetworkCreate {
48    /// Create a new network-create operation.
49    pub fn new(client: impl Into<DockerRef>, name: impl Into<String>) -> Self {
50        Self {
51            docker: client.into().0,
52            name: name.into(),
53            driver: None,
54        }
55    }
56
57    /// Set the network driver (defaults to "bridge").
58    pub fn driver(mut self, driver: impl Into<String>) -> Self {
59        self.driver = Some(driver.into());
60        self
61    }
62
63    /// Execute and return a typed result.
64    ///
65    /// # Errors
66    ///
67    /// Returns [`OperationError::External`] if the network cannot be created.
68    pub async fn run(
69        &self,
70        _ctx: &OperationContext,
71    ) -> Result<NetworkCreateOutput, OperationError> {
72        let request = NetworkCreateRequest {
73            name: self.name.clone(),
74            driver: Some(self.driver.clone().unwrap_or_else(|| "bridge".to_string())),
75            ..Default::default()
76        };
77        let response = self
78            .docker
79            .create_network(request)
80            .await
81            .map_err(docker_error)?;
82        Ok(NetworkCreateOutput { id: response.id })
83    }
84}
85
86#[async_trait]
87impl Operation for NetworkCreate {
88    fn kind(&self) -> &str {
89        "docker"
90    }
91
92    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
93        to_value(&self.run(ctx).await?)
94    }
95
96    fn input(&self) -> Option<Value> {
97        Some(serde_json::json!({
98            "operation": "network_create",
99            "name": self.name,
100        }))
101    }
102}
103
104impl TypedOperation for NetworkCreate {
105    type Output = NetworkCreateOutput;
106}
107
108// ---------------------------------------------------------------------------
109// NetworkInspect
110// ---------------------------------------------------------------------------
111
112/// Output of a network inspection.
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct NetworkInspectOutput {
115    /// The full inspection response as JSON.
116    pub data: Value,
117}
118
119/// Inspect a network.
120///
121/// # Examples
122///
123/// ```no_run
124/// use ironflow_ops_docker::networks::NetworkInspect;
125/// use ironflow_ops_docker::DockerClient;
126/// use ironflow_core::operation::Operation;
127///
128/// let client = DockerClient::connect_local().unwrap();
129/// let op = NetworkInspect::new(&client, "my-network");
130/// assert_eq!(op.kind(), "docker");
131/// ```
132pub struct NetworkInspect {
133    docker: Docker,
134    name: String,
135}
136
137impl NetworkInspect {
138    /// Create a new network-inspect operation.
139    pub fn new(client: impl Into<DockerRef>, name: impl Into<String>) -> Self {
140        Self {
141            docker: client.into().0,
142            name: name.into(),
143        }
144    }
145
146    /// Execute and return a typed result.
147    ///
148    /// # Errors
149    ///
150    /// Returns [`OperationError::External`] if the network does not exist.
151    pub async fn run(
152        &self,
153        _ctx: &OperationContext,
154    ) -> Result<NetworkInspectOutput, OperationError> {
155        let options = InspectNetworkOptions {
156            verbose: false,
157            scope: Some("local".to_string()),
158        };
159        let response = self
160            .docker
161            .inspect_network(&self.name, Some(options))
162            .await
163            .map_err(docker_error)?;
164        let data = serde_json::to_value(&response).map_err(|e| OperationError::External {
165            origin: "docker".to_string(),
166            message: e.to_string(),
167        })?;
168        Ok(NetworkInspectOutput { data })
169    }
170}
171
172#[async_trait]
173impl Operation for NetworkInspect {
174    fn kind(&self) -> &str {
175        "docker"
176    }
177
178    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
179        to_value(&self.run(ctx).await?)
180    }
181
182    fn input(&self) -> Option<Value> {
183        Some(serde_json::json!({
184            "operation": "network_inspect",
185            "name": self.name,
186        }))
187    }
188}
189
190impl TypedOperation for NetworkInspect {
191    type Output = NetworkInspectOutput;
192}
193
194// ---------------------------------------------------------------------------
195// NetworkList
196// ---------------------------------------------------------------------------
197
198/// A single entry in the network list.
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct NetworkListEntry {
201    /// Network ID.
202    pub id: String,
203    /// Network name.
204    pub name: String,
205    /// Network driver.
206    pub driver: String,
207}
208
209/// Output of a network list.
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct NetworkListOutput {
212    /// The networks.
213    pub networks: Vec<NetworkListEntry>,
214}
215
216/// List networks.
217///
218/// # Examples
219///
220/// ```no_run
221/// use ironflow_ops_docker::networks::NetworkList;
222/// use ironflow_ops_docker::DockerClient;
223/// use ironflow_core::operation::Operation;
224///
225/// let client = DockerClient::connect_local().unwrap();
226/// let op = NetworkList::new(&client);
227/// assert_eq!(op.kind(), "docker");
228/// ```
229pub struct NetworkList {
230    docker: Docker,
231    filters: HashMap<String, Vec<String>>,
232}
233
234impl NetworkList {
235    /// Create a new network-list operation.
236    pub fn new(client: impl Into<DockerRef>) -> Self {
237        Self {
238            docker: client.into().0,
239            filters: HashMap::new(),
240        }
241    }
242
243    /// Add a filter.
244    pub fn filter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
245        self.filters
246            .entry(key.into())
247            .or_default()
248            .push(value.into());
249        self
250    }
251
252    /// Execute and return a typed result.
253    ///
254    /// # Errors
255    ///
256    /// Returns [`OperationError::External`] if the Docker daemon is
257    /// unreachable.
258    pub async fn run(&self, _ctx: &OperationContext) -> Result<NetworkListOutput, OperationError> {
259        let options = ListNetworksOptions {
260            filters: Some(self.filters.clone()),
261        };
262        let networks = self
263            .docker
264            .list_networks(Some(options))
265            .await
266            .map_err(docker_error)?;
267        let entries = networks
268            .into_iter()
269            .map(|n| NetworkListEntry {
270                id: n.id.unwrap_or_default(),
271                name: n.name.unwrap_or_default(),
272                driver: n.driver.unwrap_or_default(),
273            })
274            .collect();
275        Ok(NetworkListOutput { networks: entries })
276    }
277}
278
279#[async_trait]
280impl Operation for NetworkList {
281    fn kind(&self) -> &str {
282        "docker"
283    }
284
285    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
286        to_value(&self.run(ctx).await?)
287    }
288
289    fn input(&self) -> Option<Value> {
290        Some(serde_json::json!({
291            "operation": "network_list",
292        }))
293    }
294}
295
296impl TypedOperation for NetworkList {
297    type Output = NetworkListOutput;
298}
299
300// ---------------------------------------------------------------------------
301// NetworkRemove
302// ---------------------------------------------------------------------------
303
304/// Output of a network removal.
305#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct NetworkRemoveOutput {
307    /// The removed network name.
308    pub name: String,
309}
310
311/// Remove a network.
312///
313/// # Examples
314///
315/// ```no_run
316/// use ironflow_ops_docker::networks::NetworkRemove;
317/// use ironflow_ops_docker::DockerClient;
318/// use ironflow_core::operation::Operation;
319///
320/// let client = DockerClient::connect_local().unwrap();
321/// let op = NetworkRemove::new(&client, "my-network");
322/// assert_eq!(op.kind(), "docker");
323/// ```
324pub struct NetworkRemove {
325    docker: Docker,
326    name: String,
327}
328
329impl NetworkRemove {
330    /// Create a new network-remove operation.
331    pub fn new(client: impl Into<DockerRef>, name: impl Into<String>) -> Self {
332        Self {
333            docker: client.into().0,
334            name: name.into(),
335        }
336    }
337
338    /// Execute and return a typed result.
339    ///
340    /// # Errors
341    ///
342    /// Returns [`OperationError::External`] if the network does not exist.
343    pub async fn run(
344        &self,
345        _ctx: &OperationContext,
346    ) -> Result<NetworkRemoveOutput, OperationError> {
347        self.docker
348            .remove_network(&self.name)
349            .await
350            .map_err(docker_error)?;
351        Ok(NetworkRemoveOutput {
352            name: self.name.clone(),
353        })
354    }
355}
356
357#[async_trait]
358impl Operation for NetworkRemove {
359    fn kind(&self) -> &str {
360        "docker"
361    }
362
363    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
364        to_value(&self.run(ctx).await?)
365    }
366
367    fn input(&self) -> Option<Value> {
368        Some(serde_json::json!({
369            "operation": "network_remove",
370            "name": self.name,
371        }))
372    }
373}
374
375impl TypedOperation for NetworkRemove {
376    type Output = NetworkRemoveOutput;
377}
378
379// ---------------------------------------------------------------------------
380// NetworkPrune
381// ---------------------------------------------------------------------------
382
383/// Output of pruning unused networks.
384#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct NetworkPruneOutput {
386    /// Names of removed networks.
387    pub networks_deleted: Vec<String>,
388}
389
390/// Remove unused networks.
391///
392/// # Examples
393///
394/// ```no_run
395/// use ironflow_ops_docker::networks::NetworkPrune;
396/// use ironflow_ops_docker::DockerClient;
397/// use ironflow_core::operation::Operation;
398///
399/// let client = DockerClient::connect_local().unwrap();
400/// let op = NetworkPrune::new(&client);
401/// assert_eq!(op.kind(), "docker");
402/// ```
403pub struct NetworkPrune {
404    docker: Docker,
405    filters: HashMap<String, Vec<String>>,
406}
407
408impl NetworkPrune {
409    /// Create a new network-prune operation.
410    pub fn new(client: impl Into<DockerRef>) -> Self {
411        Self {
412            docker: client.into().0,
413            filters: HashMap::new(),
414        }
415    }
416
417    /// Add a filter.
418    pub fn filter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
419        self.filters
420            .entry(key.into())
421            .or_default()
422            .push(value.into());
423        self
424    }
425
426    /// Execute and return a typed result.
427    ///
428    /// # Errors
429    ///
430    /// Returns [`OperationError::External`] if the Docker daemon is
431    /// unreachable.
432    pub async fn run(&self, _ctx: &OperationContext) -> Result<NetworkPruneOutput, OperationError> {
433        let options = PruneNetworksOptions {
434            filters: Some(self.filters.clone()),
435        };
436        let response = self
437            .docker
438            .prune_networks(Some(options))
439            .await
440            .map_err(docker_error)?;
441        Ok(NetworkPruneOutput {
442            networks_deleted: response.networks_deleted.unwrap_or_default(),
443        })
444    }
445}
446
447#[async_trait]
448impl Operation for NetworkPrune {
449    fn kind(&self) -> &str {
450        "docker"
451    }
452
453    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
454        to_value(&self.run(ctx).await?)
455    }
456
457    fn input(&self) -> Option<Value> {
458        Some(serde_json::json!({
459            "operation": "network_prune",
460        }))
461    }
462}
463
464impl TypedOperation for NetworkPrune {
465    type Output = NetworkPruneOutput;
466}