docker_pyo3/
network.rs

1use std::collections::HashMap;
2
3use crate::Pyo3Docker;
4use docker_api::opts::{ContainerConnectionOpts, EndpointIpamConfig, NetworkPruneOpts};
5use docker_api::opts::{ContainerDisconnectionOpts, NetworkCreateOpts};
6use docker_api::{models::NetworkPrune200Response, Network, Networks};
7use pyo3::exceptions;
8use pyo3::prelude::*;
9use pyo3::types::{PyDict, PyList};
10use pythonize::pythonize;
11
12#[pymodule]
13pub fn network(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
14    m.add_class::<Pyo3Networks>()?;
15    m.add_class::<Pyo3Network>()?;
16    Ok(())
17}
18
19/// Interface for managing Docker networks collection.
20#[derive(Debug)]
21#[pyclass(name = "Networks")]
22pub struct Pyo3Networks(pub Networks);
23
24/// Represents an individual Docker network.
25#[derive(Debug)]
26#[pyclass(name = "Network")]
27pub struct Pyo3Network(pub Network);
28
29#[pymethods]
30impl Pyo3Networks {
31    #[new]
32    pub fn new(docker: Pyo3Docker) -> Self {
33        Pyo3Networks(Networks::new(docker.0))
34    }
35
36    /// Get a specific network by ID or name.
37    ///
38    /// Args:
39    ///     id: Network ID or name
40    ///
41    /// Returns:
42    ///     Network: Network instance
43    pub fn get(&self, id: &str) -> Pyo3Network {
44        Pyo3Network(self.0.get(id))
45    }
46
47    /// List all networks.
48    ///
49    /// Returns:
50    ///     list[dict]: List of network information dictionaries
51    pub fn list(&self) -> PyResult<Py<PyAny>> {
52        let rv = __networks_list(&self.0);
53
54        match rv {
55            Ok(rv) => Ok(pythonize_this!(rv)),
56            Err(rv) => Err(py_sys_exception!(rv)),
57        }
58    }
59
60    /// Remove unused networks.
61    ///
62    /// Returns:
63    ///     dict: Prune results including networks deleted
64    pub fn prune(&self) -> PyResult<Py<PyAny>> {
65        let rv = __networks_prune(&self.0, &Default::default());
66
67        match rv {
68            Ok(rv) => Ok(pythonize_this!(rv)),
69            Err(rv) => Err(py_sys_exception!(rv)),
70        }
71    }
72
73    /// Create a new network.
74    ///
75    /// Args:
76    ///     name: Network name
77    ///     check_duplicate: Check for duplicate networks with the same name
78    ///     driver: Network driver (e.g., "bridge", "overlay")
79    ///     internal: Restrict external access to the network
80    ///     attachable: Enable manual container attachment
81    ///     ingress: Create an ingress network
82    ///     enable_ipv6: Enable IPv6 networking
83    ///     options: Driver-specific options as dict
84    ///     labels: Labels as dict (e.g., {"env": "prod"})
85    ///
86    /// Returns:
87    ///     Network: Created network instance
88    #[pyo3(signature = (name, *, check_duplicate=None, driver=None, internal=None, attachable=None, ingress=None, enable_ipv6=None, options=None, labels=None))]
89    pub fn create(
90        &self,
91        name: &str,
92        check_duplicate: Option<bool>,
93        driver: Option<&str>,
94        internal: Option<bool>,
95        attachable: Option<bool>,
96        ingress: Option<bool>,
97        enable_ipv6: Option<bool>,
98        options: Option<&Bound<'_, PyDict>>,
99        labels: Option<&Bound<'_, PyDict>>,
100    ) -> PyResult<Pyo3Network> {
101        let mut network_opts = NetworkCreateOpts::builder(name);
102
103        let options_map: Option<HashMap<String, String>> = if options.is_some() {
104            Some(options.unwrap().extract().unwrap())
105        } else {
106            None
107        };
108        let options: Option<HashMap<&str, &str>> = options_map
109            .as_ref()
110            .map(|m| m.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect());
111
112        let labels_map: Option<HashMap<String, String>> = if labels.is_some() {
113            Some(labels.unwrap().extract().unwrap())
114        } else {
115            None
116        };
117        let labels: Option<HashMap<&str, &str>> = labels_map
118            .as_ref()
119            .map(|m| m.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect());
120
121        bo_setter!(check_duplicate, network_opts);
122        bo_setter!(driver, network_opts);
123        bo_setter!(internal, network_opts);
124        bo_setter!(attachable, network_opts);
125        bo_setter!(ingress, network_opts);
126        bo_setter!(enable_ipv6, network_opts);
127        bo_setter!(options, network_opts);
128        bo_setter!(labels, network_opts);
129
130        let rv = __networks_create(&self.0, &network_opts.build());
131        match rv {
132            Ok(rv) => Ok(Pyo3Network(rv)),
133            Err(rv) => Err(py_sys_exception!(rv)),
134        }
135    }
136}
137
138#[tokio::main]
139async fn __networks_list(
140    networks: &Networks,
141) -> Result<Vec<docker_api::models::Network>, docker_api::Error> {
142    networks.list(&Default::default()).await
143}
144
145#[tokio::main]
146async fn __networks_prune(
147    networks: &Networks,
148    opts: &NetworkPruneOpts,
149) -> Result<NetworkPrune200Response, docker_api::Error> {
150    networks.prune(opts).await
151}
152
153#[tokio::main]
154async fn __networks_create(
155    networks: &Networks,
156    opts: &NetworkCreateOpts,
157) -> Result<Network, docker_api::Error> {
158    networks.create(opts).await
159}
160
161#[pymethods]
162impl Pyo3Network {
163    #[new]
164    pub fn new(docker: Pyo3Docker, id: &str) -> Self {
165        Pyo3Network(Network::new(docker.0, id))
166    }
167
168    /// Get the network ID.
169    ///
170    /// Returns:
171    ///     str: Network ID
172    pub fn id(&self) -> String {
173        self.0.id().to_string()
174    }
175
176    /// Inspect the network to get detailed information.
177    ///
178    /// Returns:
179    ///     dict: Detailed network information including config, containers, etc.
180    pub fn inspect(&self) -> PyResult<Py<PyAny>> {
181        let rv = __network_inspect(&self.0);
182
183        match rv {
184            Ok(rv) => Ok(pythonize_this!(rv)),
185            Err(rv) => Err(py_sys_exception!(rv)),
186        }
187    }
188
189    /// Delete the network.
190    ///
191    /// Returns:
192    ///     None
193    pub fn delete(&self) -> PyResult<()> {
194        let rv = __network_delete(&self.0);
195        match rv {
196            Ok(rv) => Ok(rv),
197            Err(rv) => Err(py_sys_exception!(rv)),
198        }
199    }
200
201    /// Connect a container to this network.
202    ///
203    /// Args:
204    ///     container_id: Container ID or name to connect
205    ///     aliases: Network aliases for the container as list
206    ///     links: Links to other containers as list
207    ///     network_id: Network ID
208    ///     endpoint_id: Endpoint ID
209    ///     gateway: IPv4 gateway address
210    ///     ipv4: IPv4 address for the container
211    ///     prefix_len: IPv4 prefix length
212    ///     ipv6_gateway: IPv6 gateway address
213    ///     ipv6: IPv6 address for the container
214    ///     ipv6_prefix_len: IPv6 prefix length
215    ///     mac: MAC address
216    ///     driver_opts: Driver-specific options as dict
217    ///     ipam_config: IPAM configuration as dict with ipv4, ipv6, link_local_ips
218    ///
219    /// Returns:
220    ///     None
221    #[pyo3(signature = (container_id, aliases=None, links=None, network_id=None, endpoint_id=None, gateway=None, ipv4=None, prefix_len=None, ipv6_gateway=None, ipv6=None, ipv6_prefix_len=None, mac=None, driver_opts=None, ipam_config=None))]
222    pub fn connect(
223        &self,
224        container_id: &str,
225        aliases: Option<&Bound<'_, PyList>>,
226        links: Option<&Bound<'_, PyList>>,
227        network_id: Option<&str>,
228        endpoint_id: Option<&str>,
229        gateway: Option<&str>,
230        ipv4: Option<&str>,
231        prefix_len: Option<isize>,
232        ipv6_gateway: Option<&str>,
233        ipv6: Option<&str>,
234        ipv6_prefix_len: Option<i64>,
235        mac: Option<&str>,
236        driver_opts: Option<&Bound<'_, PyDict>>,
237        ipam_config: Option<&Bound<'_, PyDict>>,
238    ) -> PyResult<()> {
239        let mut connect_opts = ContainerConnectionOpts::builder(container_id);
240
241        let aliases_strings: Option<Vec<String>> = if aliases.is_some() {
242            aliases.unwrap().extract().unwrap()
243        } else {
244            None
245        };
246        let aliases: Option<Vec<&str>> = aliases_strings
247            .as_ref()
248            .map(|v| v.iter().map(|s| s.as_str()).collect());
249
250        let links_strings: Option<Vec<String>> = if links.is_some() {
251            links.unwrap().extract().unwrap()
252        } else {
253            None
254        };
255        let links: Option<Vec<&str>> = links_strings
256            .as_ref()
257            .map(|v| v.iter().map(|s| s.as_str()).collect());
258
259        let driver_opts_map: Option<HashMap<String, String>> = if driver_opts.is_some() {
260            driver_opts.unwrap().extract().unwrap()
261        } else {
262            None
263        };
264        let driver_opts: Option<HashMap<&str, &str>> = driver_opts_map
265            .as_ref()
266            .map(|m| m.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect());
267
268        bo_setter!(network_id, connect_opts);
269        bo_setter!(endpoint_id, connect_opts);
270        bo_setter!(gateway, connect_opts);
271        bo_setter!(ipv4, connect_opts);
272        bo_setter!(prefix_len, connect_opts);
273        bo_setter!(ipv6_gateway, connect_opts);
274        bo_setter!(ipv6, connect_opts);
275        bo_setter!(ipv6_prefix_len, connect_opts);
276        bo_setter!(mac, connect_opts);
277
278        bo_setter!(aliases, connect_opts);
279        bo_setter!(links, connect_opts);
280        bo_setter!(driver_opts, connect_opts);
281
282        // Handle ipam_config - expects dict with ipv4, ipv6, link_local_ips
283        if let Some(ipam_dict) = ipam_config {
284            let mut config = EndpointIpamConfig::new();
285
286            if let Some(ipv4_addr) = ipam_dict.get_item("ipv4")? {
287                let ipv4_str: String = ipv4_addr.extract()?;
288                config = config.ipv4(ipv4_str);
289            }
290
291            if let Some(ipv6_addr) = ipam_dict.get_item("ipv6")? {
292                let ipv6_str: String = ipv6_addr.extract()?;
293                config = config.ipv6(ipv6_str);
294            }
295
296            if let Some(link_local) = ipam_dict.get_item("link_local_ips")? {
297                let ips: Vec<String> = link_local.extract()?;
298                config = config.link_local_ips(ips);
299            }
300
301            connect_opts = connect_opts.ipam_config(config);
302        }
303
304        let rv = __network_connect(&self.0, &connect_opts.build());
305
306        match rv {
307            Ok(rv) => Ok(rv),
308            Err(rv) => Err(py_sys_exception!(rv)),
309        }
310    }
311
312    /// Disconnect a container from this network.
313    ///
314    /// Args:
315    ///     container_id: Container ID or name to disconnect
316    ///     force: Force disconnect even if container is running
317    ///
318    /// Returns:
319    ///     None
320    #[pyo3(signature = (container_id, force=None))]
321    pub fn disconnect(&self, container_id: &str, force: Option<bool>) -> PyResult<()> {
322        let mut disconnect_opts = ContainerDisconnectionOpts::builder(container_id);
323        bo_setter!(force, disconnect_opts);
324
325        let rv = __network_disconnect(&self.0, &disconnect_opts.build());
326
327        match rv {
328            Ok(rv) => Ok(rv),
329            Err(rv) => Err(py_sys_exception!(rv)),
330        }
331    }
332}
333
334#[tokio::main]
335async fn __network_inspect(
336    network: &Network,
337) -> Result<docker_api::models::Network, docker_api::Error> {
338    network.inspect().await
339}
340
341#[tokio::main]
342async fn __network_delete(network: &Network) -> Result<(), docker_api::Error> {
343    network.delete().await
344}
345
346#[tokio::main]
347async fn __network_connect(
348    network: &Network,
349    opts: &ContainerConnectionOpts,
350) -> Result<(), docker_api::Error> {
351    network.connect(opts).await
352}
353
354#[tokio::main]
355async fn __network_disconnect(
356    network: &Network,
357    opts: &ContainerDisconnectionOpts,
358) -> Result<(), docker_api::Error> {
359    network.disconnect(opts).await
360}