use super::{CommandExecutor, CommandOutput, DockerCommand};
use crate::error::Result;
use async_trait::async_trait;
use std::collections::HashMap;
#[derive(Debug, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct BakeCommand {
targets: Vec<String>,
files: Vec<String>,
allow: Vec<String>,
builder: Option<String>,
call: Option<String>,
check: bool,
debug: bool,
list: Option<String>,
load: bool,
metadata_file: Option<String>,
no_cache: bool,
print: bool,
progress: Option<String>,
provenance: Option<String>,
pull: bool,
push: bool,
sbom: Option<String>,
set_values: HashMap<String, String>,
pub executor: CommandExecutor,
}
impl BakeCommand {
#[must_use]
pub fn new() -> Self {
Self {
targets: Vec::new(),
files: Vec::new(),
allow: Vec::new(),
builder: None,
call: None,
check: false,
debug: false,
list: None,
load: false,
metadata_file: None,
no_cache: false,
print: false,
progress: None,
provenance: None,
pull: false,
push: false,
sbom: None,
set_values: HashMap::new(),
executor: CommandExecutor::new(),
}
}
#[must_use]
pub fn target<S: Into<String>>(mut self, target: S) -> Self {
self.targets.push(target.into());
self
}
#[must_use]
pub fn targets<I, S>(mut self, targets: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.targets
.extend(targets.into_iter().map(std::convert::Into::into));
self
}
#[must_use]
pub fn file<S: Into<String>>(mut self, file: S) -> Self {
self.files.push(file.into());
self
}
#[must_use]
pub fn files<I, S>(mut self, files: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.files
.extend(files.into_iter().map(std::convert::Into::into));
self
}
#[must_use]
pub fn allow<S: Into<String>>(mut self, resource: S) -> Self {
self.allow.push(resource.into());
self
}
#[must_use]
pub fn builder<S: Into<String>>(mut self, builder: S) -> Self {
self.builder = Some(builder.into());
self
}
#[must_use]
pub fn call<S: Into<String>>(mut self, method: S) -> Self {
self.call = Some(method.into());
self
}
#[must_use]
pub fn check(mut self) -> Self {
self.check = true;
self
}
#[must_use]
pub fn debug(mut self) -> Self {
self.debug = true;
self
}
#[must_use]
pub fn list<S: Into<String>>(mut self, list_type: S) -> Self {
self.list = Some(list_type.into());
self
}
#[must_use]
pub fn load(mut self) -> Self {
self.load = true;
self
}
#[must_use]
pub fn metadata_file<S: Into<String>>(mut self, file: S) -> Self {
self.metadata_file = Some(file.into());
self
}
#[must_use]
pub fn no_cache(mut self) -> Self {
self.no_cache = true;
self
}
#[must_use]
pub fn print(mut self) -> Self {
self.print = true;
self
}
#[must_use]
pub fn progress<S: Into<String>>(mut self, progress_type: S) -> Self {
self.progress = Some(progress_type.into());
self
}
#[must_use]
pub fn provenance<S: Into<String>>(mut self, provenance: S) -> Self {
self.provenance = Some(provenance.into());
self
}
#[must_use]
pub fn pull(mut self) -> Self {
self.pull = true;
self
}
#[must_use]
pub fn push(mut self) -> Self {
self.push = true;
self
}
#[must_use]
pub fn sbom<S: Into<String>>(mut self, sbom: S) -> Self {
self.sbom = Some(sbom.into());
self
}
#[must_use]
pub fn set<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
self.set_values.insert(key.into(), value.into());
self
}
#[must_use]
pub fn set_values<I, K, V>(mut self, values: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
self.set_values
.extend(values.into_iter().map(|(k, v)| (k.into(), v.into())));
self
}
#[must_use]
pub fn target_count(&self) -> usize {
self.targets.len()
}
#[must_use]
pub fn get_targets(&self) -> &[String] {
&self.targets
}
#[must_use]
pub fn get_files(&self) -> &[String] {
&self.files
}
#[must_use]
pub fn is_push_enabled(&self) -> bool {
self.push
}
#[must_use]
pub fn is_load_enabled(&self) -> bool {
self.load
}
#[must_use]
pub fn is_dry_run(&self) -> bool {
self.print
}
}
impl Default for BakeCommand {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl DockerCommand for BakeCommand {
type Output = CommandOutput;
fn build_command_args(&self) -> Vec<String> {
let mut args = vec!["bake".to_string()];
for file in &self.files {
args.push("--file".to_string());
args.push(file.clone());
}
for allow in &self.allow {
args.push("--allow".to_string());
args.push(allow.clone());
}
if let Some(ref builder) = self.builder {
args.push("--builder".to_string());
args.push(builder.clone());
}
if let Some(ref call) = self.call {
args.push("--call".to_string());
args.push(call.clone());
}
if self.check {
args.push("--check".to_string());
}
if self.debug {
args.push("--debug".to_string());
}
if let Some(ref list) = self.list {
args.push("--list".to_string());
args.push(list.clone());
}
if self.load {
args.push("--load".to_string());
}
if let Some(ref metadata_file) = self.metadata_file {
args.push("--metadata-file".to_string());
args.push(metadata_file.clone());
}
if self.no_cache {
args.push("--no-cache".to_string());
}
if self.print {
args.push("--print".to_string());
}
if let Some(ref progress) = self.progress {
args.push("--progress".to_string());
args.push(progress.clone());
}
if let Some(ref provenance) = self.provenance {
args.push("--provenance".to_string());
args.push(provenance.clone());
}
if self.pull {
args.push("--pull".to_string());
}
if self.push {
args.push("--push".to_string());
}
if let Some(ref sbom) = self.sbom {
args.push("--sbom".to_string());
args.push(sbom.clone());
}
for (key, value) in &self.set_values {
args.push("--set".to_string());
args.push(format!("{key}={value}"));
}
args.extend(self.targets.clone());
args.extend(self.executor.raw_args.clone());
args
}
async fn execute(&self) -> Result<Self::Output> {
let args = self.build_command_args();
let command_name = args[0].clone();
let command_args = args[1..].to_vec();
self.executor
.execute_command(&command_name, command_args)
.await
}
fn get_executor(&self) -> &CommandExecutor {
&self.executor
}
fn get_executor_mut(&mut self) -> &mut CommandExecutor {
&mut self.executor
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bake_command_basic() {
let bake_cmd = BakeCommand::new();
let args = bake_cmd.build_command_args();
assert_eq!(args, vec!["bake"]); }
#[test]
fn test_bake_command_with_targets() {
let bake_cmd = BakeCommand::new()
.target("web")
.target("api")
.target("worker");
let args = bake_cmd.build_command_args();
assert!(args.contains(&"web".to_string()));
assert!(args.contains(&"api".to_string()));
assert!(args.contains(&"worker".to_string()));
assert_eq!(bake_cmd.target_count(), 3);
}
#[test]
fn test_bake_command_with_files() {
let bake_cmd = BakeCommand::new()
.file("docker-compose.yml")
.file("custom-bake.hcl");
let args = bake_cmd.build_command_args();
assert!(args.contains(&"--file".to_string()));
assert!(args.contains(&"docker-compose.yml".to_string()));
assert!(args.contains(&"custom-bake.hcl".to_string()));
assert_eq!(bake_cmd.get_files().len(), 2);
}
#[test]
fn test_bake_command_push_and_load() {
let bake_cmd = BakeCommand::new().push().load();
let args = bake_cmd.build_command_args();
assert!(args.contains(&"--push".to_string()));
assert!(args.contains(&"--load".to_string()));
assert!(bake_cmd.is_push_enabled());
assert!(bake_cmd.is_load_enabled());
}
#[test]
fn test_bake_command_with_builder() {
let bake_cmd = BakeCommand::new().builder("mybuilder");
let args = bake_cmd.build_command_args();
assert!(args.contains(&"--builder".to_string()));
assert!(args.contains(&"mybuilder".to_string()));
}
#[test]
fn test_bake_command_with_set_values() {
let bake_cmd = BakeCommand::new()
.set("web.platform", "linux/amd64,linux/arm64")
.set("*.output", "type=registry");
let args = bake_cmd.build_command_args();
assert!(args.contains(&"--set".to_string()));
assert!(args.contains(&"web.platform=linux/amd64,linux/arm64".to_string()));
assert!(args.contains(&"*.output=type=registry".to_string()));
}
#[test]
fn test_bake_command_flags() {
let bake_cmd = BakeCommand::new().check().debug().no_cache().print().pull();
let args = bake_cmd.build_command_args();
assert!(args.contains(&"--check".to_string()));
assert!(args.contains(&"--debug".to_string()));
assert!(args.contains(&"--no-cache".to_string()));
assert!(args.contains(&"--print".to_string()));
assert!(args.contains(&"--pull".to_string()));
assert!(bake_cmd.is_dry_run());
}
#[test]
fn test_bake_command_with_metadata_file() {
let bake_cmd = BakeCommand::new().metadata_file("build-metadata.json");
let args = bake_cmd.build_command_args();
assert!(args.contains(&"--metadata-file".to_string()));
assert!(args.contains(&"build-metadata.json".to_string()));
}
#[test]
fn test_bake_command_with_progress() {
let bake_cmd = BakeCommand::new().progress("plain");
let args = bake_cmd.build_command_args();
assert!(args.contains(&"--progress".to_string()));
assert!(args.contains(&"plain".to_string()));
}
#[test]
fn test_bake_command_with_attestations() {
let bake_cmd = BakeCommand::new()
.provenance("mode=max")
.sbom("generator=docker/buildkit");
let args = bake_cmd.build_command_args();
assert!(args.contains(&"--provenance".to_string()));
assert!(args.contains(&"mode=max".to_string()));
assert!(args.contains(&"--sbom".to_string()));
assert!(args.contains(&"generator=docker/buildkit".to_string()));
}
#[test]
fn test_bake_command_with_allow() {
let bake_cmd = BakeCommand::new()
.allow("network.host")
.allow("security.insecure");
let args = bake_cmd.build_command_args();
assert!(args.contains(&"--allow".to_string()));
assert!(args.contains(&"network.host".to_string()));
assert!(args.contains(&"security.insecure".to_string()));
}
#[test]
fn test_bake_command_with_call() {
let bake_cmd = BakeCommand::new().call("check");
let args = bake_cmd.build_command_args();
assert!(args.contains(&"--call".to_string()));
assert!(args.contains(&"check".to_string()));
}
#[test]
fn test_bake_command_with_list() {
let bake_cmd = BakeCommand::new().list("targets");
let args = bake_cmd.build_command_args();
assert!(args.contains(&"--list".to_string()));
assert!(args.contains(&"targets".to_string()));
}
#[test]
fn test_bake_command_extensibility() {
let mut bake_cmd = BakeCommand::new();
bake_cmd.get_executor_mut().add_arg("--experimental");
bake_cmd
.get_executor_mut()
.add_args(vec!["--custom", "value"]);
println!("Extensibility methods called successfully");
}
}