use crate::command::build::{BuildCommand, BuildOutput};
use crate::command::{CommandExecutor, DockerCommand};
use crate::error::Result;
use async_trait::async_trait;
#[derive(Debug, Clone)]
pub struct BuilderBuildCommand {
inner: BuildCommand,
}
impl BuilderBuildCommand {
pub fn new(context: impl Into<String>) -> Self {
Self {
inner: BuildCommand::new(context),
}
}
#[must_use]
pub fn dockerfile(mut self, path: impl Into<String>) -> Self {
self.inner = self.inner.file(path.into());
self
}
#[must_use]
pub fn tag(mut self, tag: impl Into<String>) -> Self {
self.inner = self.inner.tag(tag);
self
}
#[must_use]
pub fn no_cache(mut self) -> Self {
self.inner = self.inner.no_cache();
self
}
#[must_use]
pub fn build_arg(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.inner = self.inner.build_arg(key, value);
self
}
#[must_use]
pub fn target(mut self, target: impl Into<String>) -> Self {
self.inner = self.inner.target(target);
self
}
#[must_use]
pub fn platform(mut self, platform: impl Into<String>) -> Self {
self.inner = self.inner.platform(platform);
self
}
#[must_use]
pub fn buildkit(mut self) -> Self {
self.inner
.executor
.raw_args
.push("DOCKER_BUILDKIT=1".to_string());
self
}
#[must_use]
pub fn quiet(mut self) -> Self {
self.inner = self.inner.quiet();
self
}
#[must_use]
pub fn force_rm(mut self) -> Self {
self.inner = self.inner.force_rm();
self
}
#[must_use]
pub fn rm(self) -> Self {
self
}
#[must_use]
pub fn no_rm(mut self) -> Self {
self.inner = self.inner.no_rm();
self
}
#[must_use]
pub fn pull(mut self) -> Self {
self.inner = self.inner.pull();
self
}
}
#[async_trait]
impl DockerCommand for BuilderBuildCommand {
type Output = BuildOutput;
fn get_executor(&self) -> &CommandExecutor {
&self.inner.executor
}
fn get_executor_mut(&mut self) -> &mut CommandExecutor {
&mut self.inner.executor
}
fn build_command_args(&self) -> Vec<String> {
let mut inner_args = self.inner.build_command_args();
if !inner_args.is_empty() && inner_args[0] == "build" {
inner_args[0] = "builder".to_string();
inner_args.insert(1, "build".to_string());
}
inner_args
}
async fn execute(&self) -> Result<Self::Output> {
let args = self.build_command_args();
let output = self.execute_command(args).await?;
let image_id = extract_image_id(&output.stdout);
Ok(BuildOutput {
stdout: output.stdout,
stderr: output.stderr,
exit_code: output.exit_code,
image_id,
})
}
}
fn extract_image_id(stdout: &str) -> Option<String> {
for line in stdout.lines().rev() {
if line.contains("Successfully built") {
return line.split_whitespace().last().map(String::from);
}
if line.contains("writing image sha256:") {
if let Some(id) = line.split("sha256:").nth(1) {
return Some(format!(
"sha256:{}",
id.split_whitespace()
.next()?
.trim_end_matches('"')
.trim_end_matches('}')
));
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_build_basic() {
let cmd = BuilderBuildCommand::new(".");
let args = cmd.build_command_args();
assert_eq!(&args[0..2], &["builder", "build"]);
assert!(args.contains(&".".to_string()));
}
#[test]
fn test_builder_build_with_options() {
let cmd = BuilderBuildCommand::new("/app")
.tag("myapp:latest")
.dockerfile("custom.Dockerfile")
.no_cache()
.build_arg("VERSION", "1.0");
let args = cmd.build_command_args();
assert_eq!(&args[0..2], &["builder", "build"]);
assert!(args.contains(&"--tag".to_string()));
assert!(args.contains(&"myapp:latest".to_string()));
assert!(args.contains(&"--file".to_string()));
assert!(args.contains(&"custom.Dockerfile".to_string()));
assert!(args.contains(&"--no-cache".to_string()));
assert!(args.contains(&"--build-arg".to_string()));
assert!(args.contains(&"VERSION=1.0".to_string()));
}
#[test]
fn test_builder_build_buildkit() {
let mut cmd = BuilderBuildCommand::new(".");
cmd = cmd.buildkit();
assert!(cmd
.inner
.executor
.raw_args
.contains(&"DOCKER_BUILDKIT=1".to_string()));
}
#[test]
fn test_builder_build_platform() {
let cmd = BuilderBuildCommand::new(".")
.platform("linux/amd64")
.target("production");
let args = cmd.build_command_args();
assert!(args.contains(&"--platform".to_string()));
assert!(args.contains(&"linux/amd64".to_string()));
assert!(args.contains(&"--target".to_string()));
assert!(args.contains(&"production".to_string()));
}
#[test]
fn test_builder_build_extensibility() {
let mut cmd = BuilderBuildCommand::new(".");
cmd.inner
.executor
.raw_args
.push("--custom-flag".to_string());
let args = cmd.build_command_args();
assert!(args.contains(&"--custom-flag".to_string()));
}
}