docker_wrapper/command/network/
create.rs1use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone)]
10#[allow(clippy::struct_excessive_bools)]
11pub struct NetworkCreateCommand {
12 name: String,
14 driver: Option<String>,
16 driver_opts: HashMap<String, String>,
18 subnet: Option<String>,
20 ip_range: Option<String>,
22 gateway: Option<String>,
24 ipv6: bool,
26 attachable: bool,
28 internal: bool,
30 labels: HashMap<String, String>,
32 scope: Option<String>,
34 config_from: Option<String>,
36 config_only: bool,
38 ingress: bool,
40 aux_addresses: HashMap<String, String>,
42 pub executor: CommandExecutor,
44}
45
46impl NetworkCreateCommand {
47 #[must_use]
49 pub fn new(name: impl Into<String>) -> Self {
50 Self {
51 name: name.into(),
52 driver: None,
53 driver_opts: HashMap::new(),
54 subnet: None,
55 ip_range: None,
56 gateway: None,
57 ipv6: false,
58 attachable: false,
59 internal: false,
60 labels: HashMap::new(),
61 scope: None,
62 config_from: None,
63 config_only: false,
64 ingress: false,
65 aux_addresses: HashMap::new(),
66 executor: CommandExecutor::new(),
67 }
68 }
69
70 #[must_use]
72 pub fn driver(mut self, driver: impl Into<String>) -> Self {
73 self.driver = Some(driver.into());
74 self
75 }
76
77 #[must_use]
79 pub fn driver_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
80 self.driver_opts.insert(key.into(), value.into());
81 self
82 }
83
84 #[must_use]
86 pub fn subnet(mut self, subnet: impl Into<String>) -> Self {
87 self.subnet = Some(subnet.into());
88 self
89 }
90
91 #[must_use]
93 pub fn ip_range(mut self, range: impl Into<String>) -> Self {
94 self.ip_range = Some(range.into());
95 self
96 }
97
98 #[must_use]
100 pub fn gateway(mut self, gateway: impl Into<String>) -> Self {
101 self.gateway = Some(gateway.into());
102 self
103 }
104
105 #[must_use]
107 pub fn ipv6(mut self) -> Self {
108 self.ipv6 = true;
109 self
110 }
111
112 #[must_use]
114 pub fn attachable(mut self) -> Self {
115 self.attachable = true;
116 self
117 }
118
119 #[must_use]
121 pub fn internal(mut self) -> Self {
122 self.internal = true;
123 self
124 }
125
126 #[must_use]
128 pub fn label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
129 self.labels.insert(key.into(), value.into());
130 self
131 }
132
133 #[must_use]
135 pub fn scope(mut self, scope: impl Into<String>) -> Self {
136 self.scope = Some(scope.into());
137 self
138 }
139
140 #[must_use]
142 pub fn config_from(mut self, network: impl Into<String>) -> Self {
143 self.config_from = Some(network.into());
144 self
145 }
146
147 #[must_use]
149 pub fn config_only(mut self) -> Self {
150 self.config_only = true;
151 self
152 }
153
154 #[must_use]
156 pub fn ingress(mut self) -> Self {
157 self.ingress = true;
158 self
159 }
160
161 #[must_use]
163 pub fn aux_address(mut self, name: impl Into<String>, ip: impl Into<String>) -> Self {
164 self.aux_addresses.insert(name.into(), ip.into());
165 self
166 }
167
168 pub async fn run(&self) -> Result<NetworkCreateResult> {
174 self.execute().await.map(NetworkCreateResult::from)
175 }
176}
177
178#[async_trait]
179impl DockerCommand for NetworkCreateCommand {
180 type Output = CommandOutput;
181
182 fn build_command_args(&self) -> Vec<String> {
183 let mut args = vec!["network".to_string(), "create".to_string()];
184
185 if let Some(ref driver) = self.driver {
186 args.push("--driver".to_string());
187 args.push(driver.clone());
188 }
189
190 for (key, value) in &self.driver_opts {
191 args.push("--opt".to_string());
192 args.push(format!("{key}={value}"));
193 }
194
195 if let Some(ref subnet) = self.subnet {
196 args.push("--subnet".to_string());
197 args.push(subnet.clone());
198 }
199
200 if let Some(ref ip_range) = self.ip_range {
201 args.push("--ip-range".to_string());
202 args.push(ip_range.clone());
203 }
204
205 if let Some(ref gateway) = self.gateway {
206 args.push("--gateway".to_string());
207 args.push(gateway.clone());
208 }
209
210 if self.ipv6 {
211 args.push("--ipv6".to_string());
212 }
213
214 if self.attachable {
215 args.push("--attachable".to_string());
216 }
217
218 if self.internal {
219 args.push("--internal".to_string());
220 }
221
222 for (key, value) in &self.labels {
223 args.push("--label".to_string());
224 args.push(format!("{key}={value}"));
225 }
226
227 if let Some(ref scope) = self.scope {
228 args.push("--scope".to_string());
229 args.push(scope.clone());
230 }
231
232 if let Some(ref config_from) = self.config_from {
233 args.push("--config-from".to_string());
234 args.push(config_from.clone());
235 }
236
237 if self.config_only {
238 args.push("--config-only".to_string());
239 }
240
241 if self.ingress {
242 args.push("--ingress".to_string());
243 }
244
245 for (name, ip) in &self.aux_addresses {
246 args.push("--aux-address".to_string());
247 args.push(format!("{name}={ip}"));
248 }
249
250 args.push(self.name.clone());
251 args.extend(self.executor.raw_args.clone());
252 args
253 }
254
255 fn get_executor(&self) -> &CommandExecutor {
256 &self.executor
257 }
258
259 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
260 &mut self.executor
261 }
262
263 async fn execute(&self) -> Result<Self::Output> {
264 let args = self.build_command_args();
265 let command_name = args[0].clone();
266 let command_args = args[1..].to_vec();
267 self.executor
268 .execute_command(&command_name, command_args)
269 .await
270 }
271}
272
273#[derive(Debug, Clone)]
275pub struct NetworkCreateResult {
276 pub network_id: String,
278 pub raw_output: CommandOutput,
280}
281
282impl From<CommandOutput> for NetworkCreateResult {
283 fn from(output: CommandOutput) -> Self {
284 let network_id = output.stdout.trim().to_string();
285 Self {
286 network_id,
287 raw_output: output,
288 }
289 }
290}
291
292impl NetworkCreateResult {
293 #[must_use]
295 pub fn is_success(&self) -> bool {
296 self.raw_output.success
297 }
298
299 #[must_use]
301 pub fn id(&self) -> &str {
302 &self.network_id
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
311 fn test_network_create_basic() {
312 let cmd = NetworkCreateCommand::new("my-network");
313 let args = cmd.build_command_args();
314 assert_eq!(args, vec!["network", "create", "my-network"]);
315 }
316
317 #[test]
318 fn test_network_create_with_driver() {
319 let cmd = NetworkCreateCommand::new("my-network").driver("overlay");
320 let args = cmd.build_command_args();
321 assert_eq!(
322 args,
323 vec!["network", "create", "--driver", "overlay", "my-network"]
324 );
325 }
326
327 #[test]
328 fn test_network_create_with_subnet() {
329 let cmd = NetworkCreateCommand::new("my-network")
330 .subnet("172.20.0.0/16")
331 .gateway("172.20.0.1");
332 let args = cmd.build_command_args();
333 assert_eq!(
334 args,
335 vec![
336 "network",
337 "create",
338 "--subnet",
339 "172.20.0.0/16",
340 "--gateway",
341 "172.20.0.1",
342 "my-network"
343 ]
344 );
345 }
346
347 #[test]
348 fn test_network_create_all_options() {
349 let cmd = NetworkCreateCommand::new("my-network")
350 .driver("bridge")
351 .driver_opt("com.docker.network.bridge.name", "br0")
352 .subnet("172.20.0.0/16")
353 .ip_range("172.20.240.0/20")
354 .gateway("172.20.0.1")
355 .ipv6()
356 .attachable()
357 .internal()
358 .label("env", "test")
359 .aux_address("host1", "172.20.0.5");
360
361 let args = cmd.build_command_args();
362 assert!(args.contains(&"--driver".to_string()));
363 assert!(args.contains(&"--ipv6".to_string()));
364 assert!(args.contains(&"--attachable".to_string()));
365 assert!(args.contains(&"--internal".to_string()));
366 }
367}