use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
use crate::error::Result;
use async_trait::async_trait;
#[derive(Debug, Clone)]
pub struct BuilderInfo {
pub name: String,
pub driver: Option<String>,
pub is_default: bool,
pub status: Option<String>,
}
#[derive(Debug, Clone)]
pub struct BuildxLsResult {
pub builders: Vec<BuilderInfo>,
pub output: String,
pub success: bool,
}
impl BuildxLsResult {
fn parse(output: &CommandOutput) -> Self {
let stdout = &output.stdout;
let mut builders = Vec::new();
for line in stdout.lines().skip(1) {
let line = line.trim();
if line.is_empty() || line.starts_with(' ') {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.is_empty() {
continue;
}
let mut name = parts[0].to_string();
let is_default = name.ends_with('*') || parts.get(1) == Some(&"*");
if name.ends_with('*') {
name = name.trim_end_matches('*').to_string();
}
let driver = if is_default && parts.len() > 2 {
Some(parts[2].to_string())
} else if !is_default && parts.len() > 1 {
Some(parts[1].to_string())
} else {
None
};
if name.contains('/') && !name.starts_with('/') {
continue;
}
builders.push(BuilderInfo {
name,
driver,
is_default,
status: None,
});
}
Self {
builders,
output: stdout.clone(),
success: output.success,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct BuildxLsCommand {
format: Option<String>,
no_trunc: bool,
pub executor: CommandExecutor,
}
impl BuildxLsCommand {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn format(mut self, format: impl Into<String>) -> Self {
self.format = Some(format.into());
self
}
#[must_use]
pub fn no_trunc(mut self) -> Self {
self.no_trunc = true;
self
}
fn build_args(&self) -> Vec<String> {
let mut args = vec!["buildx".to_string(), "ls".to_string()];
if let Some(ref format) = self.format {
args.push("--format".to_string());
args.push(format.clone());
}
if self.no_trunc {
args.push("--no-trunc".to_string());
}
args.extend(self.executor.raw_args.clone());
args
}
}
#[async_trait]
impl DockerCommand for BuildxLsCommand {
type Output = BuildxLsResult;
fn get_executor(&self) -> &CommandExecutor {
&self.executor
}
fn get_executor_mut(&mut self) -> &mut CommandExecutor {
&mut self.executor
}
fn build_command_args(&self) -> Vec<String> {
self.build_args()
}
async fn execute(&self) -> Result<Self::Output> {
let args = self.build_args();
let output = self.execute_command(args).await?;
Ok(BuildxLsResult::parse(&output))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_buildx_ls_basic() {
let cmd = BuildxLsCommand::new();
let args = cmd.build_args();
assert_eq!(args, vec!["buildx", "ls"]);
}
#[test]
fn test_buildx_ls_with_format() {
let cmd = BuildxLsCommand::new().format("json");
let args = cmd.build_args();
assert!(args.contains(&"--format".to_string()));
assert!(args.contains(&"json".to_string()));
}
#[test]
fn test_buildx_ls_with_no_trunc() {
let cmd = BuildxLsCommand::new().no_trunc();
let args = cmd.build_args();
assert!(args.contains(&"--no-trunc".to_string()));
}
#[test]
fn test_buildx_ls_result_parse() {
let output = CommandOutput {
stdout: "NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS\ndefault * docker\nmybuilder docker-container running v0.12.5 linux/amd64".to_string(),
stderr: String::new(),
exit_code: 0,
success: true,
};
let result = BuildxLsResult::parse(&output);
assert_eq!(result.builders.len(), 2);
assert!(result.builders[0].is_default);
assert_eq!(result.builders[0].name, "default");
assert_eq!(result.builders[1].name, "mybuilder");
}
}