use anyhow::Result;
use buildkit_client::{BuildConfig, BuildKitClient, Platform, RegistryAuth};
use buildkit_client::progress::{ConsoleProgressHandler, JsonProgressHandler};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "buildkit-client")]
#[command(about = "BuildKit Rust client for building container images", long_about = None)]
struct Cli {
#[arg(short, long, default_value = "http://localhost:1234")]
addr: String,
#[arg(short, long)]
verbose: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Local {
#[arg(short, long, default_value = ".")]
context: PathBuf,
#[arg(short = 'f', long)]
dockerfile: Option<PathBuf>,
#[arg(short, long)]
tag: Vec<String>,
#[arg(long)]
build_arg: Vec<String>,
#[arg(long)]
target: Option<String>,
#[arg(long)]
platform: Vec<String>,
#[arg(long)]
registry_host: Option<String>,
#[arg(long)]
registry_user: Option<String>,
#[arg(long)]
registry_password: Option<String>,
#[arg(long)]
no_cache: bool,
#[arg(long)]
pull: bool,
#[arg(long)]
json: bool,
},
Github {
repo: String,
#[arg(short = 'b', long)]
git_ref: Option<String>,
#[arg(long, env = "GITHUB_TOKEN")]
token: Option<String>,
#[arg(short = 'f', long)]
dockerfile: Option<String>,
#[arg(short, long)]
tag: Vec<String>,
#[arg(long)]
build_arg: Vec<String>,
#[arg(long)]
target: Option<String>,
#[arg(long)]
platform: Vec<String>,
#[arg(long)]
registry_host: Option<String>,
#[arg(long)]
registry_user: Option<String>,
#[arg(long)]
registry_password: Option<String>,
#[arg(long)]
no_cache: bool,
#[arg(long)]
pull: bool,
#[arg(long)]
json: bool,
},
Health,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let log_level = if cli.verbose { "debug" } else { "info" };
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(log_level)),
)
.init();
let mut client = BuildKitClient::connect(&cli.addr).await?;
match cli.command {
Commands::Local {
context,
dockerfile,
tag,
build_arg,
target,
platform,
registry_host,
registry_user,
registry_password,
no_cache,
pull,
json,
} => {
let mut config = BuildConfig::local(context);
if let Some(df) = dockerfile {
config = config.dockerfile(df.to_string_lossy().to_string());
}
for t in tag {
config = config.tag(t);
}
for arg in build_arg {
if let Some((key, value)) = arg.split_once('=') {
config = config.build_arg(key, value);
}
}
if let Some(t) = target {
config = config.target(t);
}
if !platform.is_empty() {
config.platforms.clear();
for p in platform {
config = config.platform(Platform::parse(&p)?);
}
}
if let (Some(host), Some(user), Some(pass)) =
(registry_host, registry_user, registry_password)
{
config = config.registry_auth(RegistryAuth {
host,
username: user,
password: pass,
});
}
config = config.no_cache(no_cache).pull(pull);
let progress: Box<dyn buildkit_client::progress::ProgressHandler> = if json {
Box::new(JsonProgressHandler::new())
} else {
Box::new(ConsoleProgressHandler::new(cli.verbose))
};
let result = client.build(config, Some(progress)).await?;
if let Some(digest) = result.digest {
println!("\n📦 Image digest: {}", digest);
}
}
Commands::Github {
repo,
git_ref,
token,
dockerfile,
tag,
build_arg,
target,
platform,
registry_host,
registry_user,
registry_password,
no_cache,
pull,
json,
} => {
let mut config = BuildConfig::github(repo);
if let Some(git_ref) = git_ref {
config = config.git_ref(git_ref);
}
if let Some(token) = token {
config = config.github_token(token);
}
if let Some(df) = dockerfile {
config = config.dockerfile(df);
}
for t in tag {
config = config.tag(t);
}
for arg in build_arg {
if let Some((key, value)) = arg.split_once('=') {
config = config.build_arg(key, value);
}
}
if let Some(t) = target {
config = config.target(t);
}
if !platform.is_empty() {
config.platforms.clear();
for p in platform {
config = config.platform(Platform::parse(&p)?);
}
}
if let (Some(host), Some(user), Some(pass)) =
(registry_host, registry_user, registry_password)
{
config = config.registry_auth(RegistryAuth {
host,
username: user,
password: pass,
});
}
config = config.no_cache(no_cache).pull(pull);
let progress: Box<dyn buildkit_client::progress::ProgressHandler> = if json {
Box::new(JsonProgressHandler::new())
} else {
Box::new(ConsoleProgressHandler::new(cli.verbose))
};
let result = client.build(config, Some(progress)).await?;
if let Some(digest) = result.digest {
println!("\n📦 Image digest: {}", digest);
}
}
Commands::Health => {
client.health_check().await?;
println!("✅ BuildKit is healthy");
}
}
Ok(())
}