docker_wrapper/command/network/
connect.rs

1//! Docker network connect command implementation.
2
3use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Docker network connect command builder
8#[derive(Debug, Clone)]
9pub struct NetworkConnectCommand {
10    /// Network name
11    network: String,
12    /// Container name or ID
13    container: String,
14    /// IPv4 address
15    ipv4: Option<String>,
16    /// IPv6 address
17    ipv6: Option<String>,
18    /// Container alias
19    alias: Vec<String>,
20    /// Link to another container
21    link: Vec<String>,
22    /// Link-local IP addresses
23    link_local_ip: Vec<String>,
24    /// Driver options
25    driver_opt: Vec<(String, String)>,
26    /// Command executor
27    pub executor: CommandExecutor,
28}
29
30impl NetworkConnectCommand {
31    /// Create a new network connect command
32    #[must_use]
33    pub fn new(network: impl Into<String>, container: impl Into<String>) -> Self {
34        Self {
35            network: network.into(),
36            container: container.into(),
37            ipv4: None,
38            ipv6: None,
39            alias: Vec::new(),
40            link: Vec::new(),
41            link_local_ip: Vec::new(),
42            driver_opt: Vec::new(),
43            executor: CommandExecutor::new(),
44        }
45    }
46
47    /// Set IPv4 address
48    #[must_use]
49    pub fn ipv4(mut self, ip: impl Into<String>) -> Self {
50        self.ipv4 = Some(ip.into());
51        self
52    }
53
54    /// Set IPv6 address
55    #[must_use]
56    pub fn ipv6(mut self, ip: impl Into<String>) -> Self {
57        self.ipv6 = Some(ip.into());
58        self
59    }
60
61    /// Add a network-scoped alias
62    #[must_use]
63    pub fn alias(mut self, alias: impl Into<String>) -> Self {
64        self.alias.push(alias.into());
65        self
66    }
67
68    /// Add a link to another container
69    #[must_use]
70    pub fn link(mut self, container: impl Into<String>) -> Self {
71        self.link.push(container.into());
72        self
73    }
74
75    /// Add a link-local IP address
76    #[must_use]
77    pub fn link_local_ip(mut self, ip: impl Into<String>) -> Self {
78        self.link_local_ip.push(ip.into());
79        self
80    }
81
82    /// Add a driver option
83    #[must_use]
84    pub fn driver_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
85        self.driver_opt.push((key.into(), value.into()));
86        self
87    }
88
89    /// Execute the command
90    ///
91    /// # Errors
92    ///
93    /// Returns an error if the Docker daemon is not running or the command fails
94    pub async fn run(&self) -> Result<NetworkConnectResult> {
95        self.execute().await.map(NetworkConnectResult::from)
96    }
97}
98
99#[async_trait]
100impl DockerCommand for NetworkConnectCommand {
101    type Output = CommandOutput;
102
103    fn build_command_args(&self) -> Vec<String> {
104        let mut args = vec!["network".to_string(), "connect".to_string()];
105
106        if let Some(ref ip) = self.ipv4 {
107            args.push("--ip".to_string());
108            args.push(ip.clone());
109        }
110
111        if let Some(ref ip) = self.ipv6 {
112            args.push("--ip6".to_string());
113            args.push(ip.clone());
114        }
115
116        for alias in &self.alias {
117            args.push("--alias".to_string());
118            args.push(alias.clone());
119        }
120
121        for link in &self.link {
122            args.push("--link".to_string());
123            args.push(link.clone());
124        }
125
126        for ip in &self.link_local_ip {
127            args.push("--link-local-ip".to_string());
128            args.push(ip.clone());
129        }
130
131        for (key, value) in &self.driver_opt {
132            args.push("--driver-opt".to_string());
133            args.push(format!("{key}={value}"));
134        }
135
136        args.push(self.network.clone());
137        args.push(self.container.clone());
138        args.extend(self.executor.raw_args.clone());
139        args
140    }
141
142    fn get_executor(&self) -> &CommandExecutor {
143        &self.executor
144    }
145
146    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
147        &mut self.executor
148    }
149
150    async fn execute(&self) -> Result<Self::Output> {
151        let args = self.build_command_args();
152        let command_name = args[0].clone();
153        let command_args = args[1..].to_vec();
154        self.executor
155            .execute_command(&command_name, command_args)
156            .await
157    }
158}
159
160/// Result from network connect command
161#[derive(Debug, Clone)]
162pub struct NetworkConnectResult {
163    /// Raw command output
164    pub raw_output: CommandOutput,
165}
166
167impl From<CommandOutput> for NetworkConnectResult {
168    fn from(output: CommandOutput) -> Self {
169        Self { raw_output: output }
170    }
171}
172
173impl NetworkConnectResult {
174    /// Check if the command was successful
175    #[must_use]
176    pub fn is_success(&self) -> bool {
177        self.raw_output.success
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_network_connect_basic() {
187        let cmd = NetworkConnectCommand::new("my-network", "my-container");
188        let args = cmd.build_command_args();
189        assert_eq!(
190            args,
191            vec!["network", "connect", "my-network", "my-container"]
192        );
193    }
194
195    #[test]
196    fn test_network_connect_with_ip() {
197        let cmd = NetworkConnectCommand::new("my-network", "my-container").ipv4("172.20.0.10");
198        let args = cmd.build_command_args();
199        assert_eq!(
200            args,
201            vec![
202                "network",
203                "connect",
204                "--ip",
205                "172.20.0.10",
206                "my-network",
207                "my-container"
208            ]
209        );
210    }
211
212    #[test]
213    fn test_network_connect_with_alias() {
214        let cmd = NetworkConnectCommand::new("my-network", "my-container")
215            .alias("db")
216            .alias("database");
217        let args = cmd.build_command_args();
218        assert!(args.contains(&"--alias".to_string()));
219        assert!(args.contains(&"db".to_string()));
220        assert!(args.contains(&"database".to_string()));
221    }
222}