docker_wrapper/command/network/
ls.rs1use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
11pub struct NetworkLsCommand {
12 filters: HashMap<String, String>,
14 format: Option<String>,
16 no_trunc: bool,
18 quiet: bool,
20 pub executor: CommandExecutor,
22}
23
24impl NetworkLsCommand {
25 #[must_use]
27 pub fn new() -> Self {
28 Self {
29 filters: HashMap::new(),
30 format: None,
31 no_trunc: false,
32 quiet: false,
33 executor: CommandExecutor::new(),
34 }
35 }
36
37 #[must_use]
39 pub fn filter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
40 self.filters.insert(key.into(), value.into());
41 self
42 }
43
44 #[must_use]
46 pub fn driver_filter(self, driver: impl Into<String>) -> Self {
47 self.filter("driver", driver)
48 }
49
50 #[must_use]
52 pub fn id_filter(self, id: impl Into<String>) -> Self {
53 self.filter("id", id)
54 }
55
56 #[must_use]
58 pub fn label_filter(self, label: impl Into<String>) -> Self {
59 self.filter("label", label)
60 }
61
62 #[must_use]
64 pub fn name_filter(self, name: impl Into<String>) -> Self {
65 self.filter("name", name)
66 }
67
68 #[must_use]
70 pub fn scope_filter(self, scope: impl Into<String>) -> Self {
71 self.filter("scope", scope)
72 }
73
74 #[must_use]
76 pub fn type_filter(self, typ: impl Into<String>) -> Self {
77 self.filter("type", typ)
78 }
79
80 #[must_use]
82 pub fn format(mut self, format: impl Into<String>) -> Self {
83 self.format = Some(format.into());
84 self
85 }
86
87 #[must_use]
89 pub fn format_json(self) -> Self {
90 self.format("json")
91 }
92
93 #[must_use]
95 pub fn no_trunc(mut self) -> Self {
96 self.no_trunc = true;
97 self
98 }
99
100 #[must_use]
102 pub fn quiet(mut self) -> Self {
103 self.quiet = true;
104 self
105 }
106
107 pub async fn run(&self) -> Result<NetworkLsOutput> {
113 self.execute().await.map(NetworkLsOutput::from)
114 }
115}
116
117impl Default for NetworkLsCommand {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123#[async_trait]
124impl DockerCommand for NetworkLsCommand {
125 type Output = CommandOutput;
126
127 fn build_command_args(&self) -> Vec<String> {
128 let mut args = vec!["network".to_string(), "ls".to_string()];
129
130 for (key, value) in &self.filters {
131 args.push("--filter".to_string());
132 args.push(format!("{key}={value}"));
133 }
134
135 if let Some(ref format) = self.format {
136 args.push("--format".to_string());
137 args.push(format.clone());
138 }
139
140 if self.no_trunc {
141 args.push("--no-trunc".to_string());
142 }
143
144 if self.quiet {
145 args.push("--quiet".to_string());
146 }
147
148 args.extend(self.executor.raw_args.clone());
149 args
150 }
151
152 fn get_executor(&self) -> &CommandExecutor {
153 &self.executor
154 }
155
156 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
157 &mut self.executor
158 }
159
160 async fn execute(&self) -> Result<Self::Output> {
161 let args = self.build_command_args();
162 let command_name = args[0].clone();
163 let command_args = args[1..].to_vec();
164 self.executor
165 .execute_command(&command_name, command_args)
166 .await
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172#[serde(rename_all = "PascalCase")]
173#[allow(clippy::struct_excessive_bools)]
174pub struct NetworkInfo {
175 #[serde(rename = "ID", default)]
177 pub id: String,
178 #[serde(default)]
180 pub name: String,
181 #[serde(default)]
183 pub driver: String,
184 #[serde(default)]
186 pub scope: String,
187 #[serde(rename = "IPv6", default)]
189 pub ipv6: bool,
190 #[serde(default)]
192 pub internal: bool,
193 #[serde(default)]
195 pub attachable: bool,
196 #[serde(default)]
198 pub ingress: bool,
199 #[serde(rename = "CreatedAt", default)]
201 pub created_at: String,
202 #[serde(default)]
204 pub labels: HashMap<String, String>,
205}
206
207#[derive(Debug, Clone)]
209pub struct NetworkLsOutput {
210 pub networks: Vec<NetworkInfo>,
212 pub raw_output: CommandOutput,
214}
215
216impl From<CommandOutput> for NetworkLsOutput {
217 fn from(output: CommandOutput) -> Self {
218 let networks = if output.stdout.starts_with('[') {
219 serde_json::from_str(&output.stdout).unwrap_or_default()
221 } else if output.stdout.trim().is_empty() {
222 vec![]
223 } else {
224 parse_table_output(&output.stdout)
226 };
227
228 Self {
229 networks,
230 raw_output: output,
231 }
232 }
233}
234
235impl NetworkLsOutput {
236 #[must_use]
238 pub fn is_success(&self) -> bool {
239 self.raw_output.success
240 }
241
242 #[must_use]
244 pub fn count(&self) -> usize {
245 self.networks.len()
246 }
247
248 #[must_use]
250 pub fn is_empty(&self) -> bool {
251 self.networks.is_empty()
252 }
253
254 #[must_use]
256 pub fn get_network(&self, name: &str) -> Option<&NetworkInfo> {
257 self.networks.iter().find(|n| n.name == name)
258 }
259
260 #[must_use]
262 pub fn ids(&self) -> Vec<String> {
263 self.networks.iter().map(|n| n.id.clone()).collect()
264 }
265}
266
267fn parse_table_output(output: &str) -> Vec<NetworkInfo> {
268 let mut networks = Vec::new();
269 let lines: Vec<&str> = output.lines().collect();
270
271 if lines.len() <= 1 {
272 return networks;
273 }
274
275 for line in lines.iter().skip(1) {
277 let parts: Vec<&str> = line.split_whitespace().collect();
278 if parts.len() >= 4 {
279 networks.push(NetworkInfo {
280 id: parts[0].to_string(),
281 name: parts[1].to_string(),
282 driver: parts[2].to_string(),
283 scope: parts[3].to_string(),
284 ipv6: false,
285 internal: false,
286 attachable: false,
287 ingress: false,
288 created_at: String::new(),
289 labels: HashMap::new(),
290 });
291 }
292 }
293
294 networks
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_network_ls_basic() {
303 let cmd = NetworkLsCommand::new();
304 let args = cmd.build_command_args();
305 assert_eq!(args, vec!["network", "ls"]);
306 }
307
308 #[test]
309 fn test_network_ls_with_filters() {
310 let cmd = NetworkLsCommand::new()
311 .driver_filter("bridge")
312 .name_filter("my-network");
313 let args = cmd.build_command_args();
314 assert!(args.contains(&"--filter".to_string()));
315 assert!(args.iter().any(|a| a.contains("driver=bridge")));
316 assert!(args.iter().any(|a| a.contains("name=my-network")));
317 }
318
319 #[test]
320 fn test_network_ls_with_format() {
321 let cmd = NetworkLsCommand::new().format_json();
322 let args = cmd.build_command_args();
323 assert_eq!(args, vec!["network", "ls", "--format", "json"]);
324 }
325
326 #[test]
327 fn test_network_ls_quiet() {
328 let cmd = NetworkLsCommand::new().quiet();
329 let args = cmd.build_command_args();
330 assert_eq!(args, vec!["network", "ls", "--quiet"]);
331 }
332
333 #[test]
334 fn test_parse_table_output() {
335 let output = "NETWORK ID NAME DRIVER SCOPE
336f2de39df4171 bridge bridge local
3379fb1e39c5d12 host host local
33894b82a6c5b45 none null local";
339
340 let networks = parse_table_output(output);
341 assert_eq!(networks.len(), 3);
342 assert_eq!(networks[0].name, "bridge");
343 assert_eq!(networks[1].name, "host");
344 assert_eq!(networks[2].name, "none");
345 }
346}