docker_wrapper/command/builder/
ls.rs

1//! Docker buildx ls command implementation.
2
3use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Information about a builder instance
8#[derive(Debug, Clone)]
9pub struct BuilderInfo {
10    /// The name of the builder
11    pub name: String,
12    /// The driver used by the builder
13    pub driver: Option<String>,
14    /// Whether this is the current/default builder
15    pub is_default: bool,
16    /// The status of the builder
17    pub status: Option<String>,
18}
19
20/// Result of buildx ls command
21#[derive(Debug, Clone)]
22pub struct BuildxLsResult {
23    /// List of builder instances
24    pub builders: Vec<BuilderInfo>,
25    /// Raw output from the command
26    pub output: String,
27    /// Whether the command succeeded
28    pub success: bool,
29}
30
31impl BuildxLsResult {
32    /// Parse the buildx ls output
33    fn parse(output: &CommandOutput) -> Self {
34        let stdout = &output.stdout;
35        let mut builders = Vec::new();
36
37        // Parse table output (skip header line)
38        for line in stdout.lines().skip(1) {
39            let line = line.trim();
40            if line.is_empty() || line.starts_with(' ') {
41                continue;
42            }
43
44            // Parse the line - format is typically:
45            // NAME/NODE       DRIVER/ENDPOINT  STATUS   BUILDKIT PLATFORMS
46            // default *       docker
47            // mybuilder       docker-container running  v0.12.5  linux/amd64
48            let parts: Vec<&str> = line.split_whitespace().collect();
49            if parts.is_empty() {
50                continue;
51            }
52
53            let mut name = parts[0].to_string();
54            let is_default = name.ends_with('*') || parts.get(1) == Some(&"*");
55
56            // Remove the asterisk from name if present
57            if name.ends_with('*') {
58                name = name.trim_end_matches('*').to_string();
59            }
60
61            let driver = if is_default && parts.len() > 2 {
62                Some(parts[2].to_string())
63            } else if !is_default && parts.len() > 1 {
64                Some(parts[1].to_string())
65            } else {
66                None
67            };
68
69            // Skip node entries (indented or contain /)
70            if name.contains('/') && !name.starts_with('/') {
71                continue;
72            }
73
74            builders.push(BuilderInfo {
75                name,
76                driver,
77                is_default,
78                status: None,
79            });
80        }
81
82        Self {
83            builders,
84            output: stdout.clone(),
85            success: output.success,
86        }
87    }
88}
89
90/// Docker buildx ls command builder
91///
92/// Lists builder instances.
93///
94/// # Example
95///
96/// ```rust,no_run
97/// use docker_wrapper::{DockerCommand, BuildxLsCommand};
98///
99/// # #[tokio::main]
100/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
101/// let result = BuildxLsCommand::new()
102///     .execute()
103///     .await?;
104///
105/// for builder in &result.builders {
106///     println!("Builder: {} (default: {})", builder.name, builder.is_default);
107/// }
108/// # Ok(())
109/// # }
110/// ```
111#[derive(Debug, Clone, Default)]
112pub struct BuildxLsCommand {
113    /// Format the output
114    format: Option<String>,
115    /// Don't truncate output
116    no_trunc: bool,
117    /// Command executor
118    pub executor: CommandExecutor,
119}
120
121impl BuildxLsCommand {
122    /// Create a new buildx ls command
123    #[must_use]
124    pub fn new() -> Self {
125        Self::default()
126    }
127
128    /// Set the output format
129    #[must_use]
130    pub fn format(mut self, format: impl Into<String>) -> Self {
131        self.format = Some(format.into());
132        self
133    }
134
135    /// Don't truncate output
136    #[must_use]
137    pub fn no_trunc(mut self) -> Self {
138        self.no_trunc = true;
139        self
140    }
141
142    /// Build the command arguments
143    fn build_args(&self) -> Vec<String> {
144        let mut args = vec!["buildx".to_string(), "ls".to_string()];
145
146        if let Some(ref format) = self.format {
147            args.push("--format".to_string());
148            args.push(format.clone());
149        }
150
151        if self.no_trunc {
152            args.push("--no-trunc".to_string());
153        }
154
155        args.extend(self.executor.raw_args.clone());
156
157        args
158    }
159}
160
161#[async_trait]
162impl DockerCommand for BuildxLsCommand {
163    type Output = BuildxLsResult;
164
165    fn get_executor(&self) -> &CommandExecutor {
166        &self.executor
167    }
168
169    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
170        &mut self.executor
171    }
172
173    fn build_command_args(&self) -> Vec<String> {
174        self.build_args()
175    }
176
177    async fn execute(&self) -> Result<Self::Output> {
178        let args = self.build_args();
179        let output = self.execute_command(args).await?;
180        Ok(BuildxLsResult::parse(&output))
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_buildx_ls_basic() {
190        let cmd = BuildxLsCommand::new();
191        let args = cmd.build_args();
192        assert_eq!(args, vec!["buildx", "ls"]);
193    }
194
195    #[test]
196    fn test_buildx_ls_with_format() {
197        let cmd = BuildxLsCommand::new().format("json");
198        let args = cmd.build_args();
199        assert!(args.contains(&"--format".to_string()));
200        assert!(args.contains(&"json".to_string()));
201    }
202
203    #[test]
204    fn test_buildx_ls_with_no_trunc() {
205        let cmd = BuildxLsCommand::new().no_trunc();
206        let args = cmd.build_args();
207        assert!(args.contains(&"--no-trunc".to_string()));
208    }
209
210    #[test]
211    fn test_buildx_ls_result_parse() {
212        let output = CommandOutput {
213            stdout: "NAME/NODE       DRIVER/ENDPOINT  STATUS   BUILDKIT PLATFORMS\ndefault *       docker\nmybuilder       docker-container running  v0.12.5  linux/amd64".to_string(),
214            stderr: String::new(),
215            exit_code: 0,
216            success: true,
217        };
218        let result = BuildxLsResult::parse(&output);
219        assert_eq!(result.builders.len(), 2);
220        assert!(result.builders[0].is_default);
221        assert_eq!(result.builders[0].name, "default");
222        assert_eq!(result.builders[1].name, "mybuilder");
223    }
224}