docker_wrapper/command/
bake.rs

1//! Docker Bake Command Implementation
2//!
3//! This module provides a comprehensive implementation of the `docker bake` command,
4//! supporting all native Docker buildx bake options for building from configuration files.
5//!
6//! # Examples
7//!
8//! ## Basic Usage
9//!
10//! ```no_run
11//! use docker_wrapper::BakeCommand;
12//! use docker_wrapper::DockerCommand;
13//!
14//! #[tokio::main]
15//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
16//!     // Basic bake with default docker-bake.hcl file
17//!     let bake_cmd = BakeCommand::new();
18//!     let output = bake_cmd.execute().await?;
19//!     println!("Bake completed: {}", output.success);
20//!     Ok(())
21//! }
22//! ```
23//!
24//! ## Advanced Usage
25//!
26//! ```no_run
27//! use docker_wrapper::BakeCommand;
28//! use docker_wrapper::DockerCommand;
29//!
30//! #[tokio::main]
31//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
32//!     // Advanced bake with custom file and targets
33//!     let bake_cmd = BakeCommand::new()
34//!         .file("docker-compose.yml")
35//!         .file("custom-bake.hcl")
36//!         .target("web")
37//!         .target("api")
38//!         .push()
39//!         .no_cache()
40//!         .set("web.platform", "linux/amd64,linux/arm64")
41//!         .metadata_file("build-metadata.json");
42//!
43//!     let output = bake_cmd.execute().await?;
44//!     println!("Multi-target bake completed: {}", output.success);
45//!     Ok(())
46//! }
47//! ```
48
49use super::{CommandExecutor, CommandOutput, DockerCommand};
50use crate::error::Result;
51use async_trait::async_trait;
52use std::collections::HashMap;
53use std::ffi::OsStr;
54
55/// Docker Bake Command Builder
56///
57/// Implements the `docker bake` command for building from configuration files
58/// like docker-compose.yml, docker-bake.hcl, or custom bake definitions.
59///
60/// # Docker Bake Overview
61///
62/// The bake command allows you to build multiple targets defined in configuration
63/// files, supporting advanced features like:
64/// - Multi-platform builds
65/// - Build matrix configurations
66/// - Shared build contexts
67/// - Variable substitution
68/// - Target dependencies
69///
70/// # Supported File Formats
71///
72/// - `docker-compose.yml` - Docker Compose service definitions
73/// - `docker-bake.hcl` - HCL (`HashiCorp` Configuration Language) format
74/// - `docker-bake.json` - JSON format
75/// - Custom build definition files
76///
77/// # Examples
78///
79/// ```no_run
80/// use docker_wrapper::BakeCommand;
81/// use docker_wrapper::DockerCommand;
82///
83/// #[tokio::main]
84/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
85///     // Build all targets from docker-compose.yml
86///     let output = BakeCommand::new()
87///         .file("docker-compose.yml")
88///         .execute()
89///         .await?;
90///
91///     println!("Bake success: {}", output.success);
92///     Ok(())
93/// }
94/// ```
95#[derive(Debug, Clone)]
96#[allow(clippy::struct_excessive_bools)]
97pub struct BakeCommand {
98    /// Build targets to build (defaults to all targets if empty)
99    targets: Vec<String>,
100    /// Build definition files
101    files: Vec<String>,
102    /// Resource access permissions
103    allow: Vec<String>,
104    /// Builder instance override
105    builder: Option<String>,
106    /// Evaluation method (build, check, outline, targets)
107    call: Option<String>,
108    /// Enable check mode (shorthand for --call=check)
109    check: bool,
110    /// Enable debug logging
111    debug: bool,
112    /// List targets or variables
113    list: Option<String>,
114    /// Load images to Docker daemon (shorthand for --set=*.output=type=docker)
115    load: bool,
116    /// Build result metadata file
117    metadata_file: Option<String>,
118    /// Disable build cache
119    no_cache: bool,
120    /// Print options without building
121    print: bool,
122    /// Progress output type
123    progress: Option<String>,
124    /// Provenance attestation (shorthand for --set=*.attest=type=provenance)
125    provenance: Option<String>,
126    /// Always pull referenced images
127    pull: bool,
128    /// Push images to registry (shorthand for --set=*.output=type=registry)
129    push: bool,
130    /// SBOM attestation (shorthand for --set=*.attest=type=sbom)
131    sbom: Option<String>,
132    /// Target value overrides (key=value pairs)
133    set_values: HashMap<String, String>,
134    /// Command executor for handling raw arguments and execution
135    executor: CommandExecutor,
136}
137
138impl BakeCommand {
139    /// Create a new `BakeCommand` instance
140    ///
141    /// # Examples
142    ///
143    /// ```
144    /// use docker_wrapper::BakeCommand;
145    ///
146    /// let bake_cmd = BakeCommand::new();
147    /// ```
148    #[must_use]
149    pub fn new() -> Self {
150        Self {
151            targets: Vec::new(),
152            files: Vec::new(),
153            allow: Vec::new(),
154            builder: None,
155            call: None,
156            check: false,
157            debug: false,
158            list: None,
159            load: false,
160            metadata_file: None,
161            no_cache: false,
162            print: false,
163            progress: None,
164            provenance: None,
165            pull: false,
166            push: false,
167            sbom: None,
168            set_values: HashMap::new(),
169            executor: CommandExecutor::new(),
170        }
171    }
172
173    /// Add a target to build
174    ///
175    /// Multiple targets can be specified. If no targets are specified,
176    /// all targets defined in the bake file will be built.
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// use docker_wrapper::BakeCommand;
182    ///
183    /// let bake_cmd = BakeCommand::new()
184    ///     .target("web")
185    ///     .target("api")
186    ///     .target("worker");
187    /// ```
188    #[must_use]
189    pub fn target<S: Into<String>>(mut self, target: S) -> Self {
190        self.targets.push(target.into());
191        self
192    }
193
194    /// Add multiple targets to build
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// use docker_wrapper::BakeCommand;
200    ///
201    /// let bake_cmd = BakeCommand::new()
202    ///     .targets(vec!["web", "api", "worker"]);
203    /// ```
204    #[must_use]
205    pub fn targets<I, S>(mut self, targets: I) -> Self
206    where
207        I: IntoIterator<Item = S>,
208        S: Into<String>,
209    {
210        self.targets
211            .extend(targets.into_iter().map(std::convert::Into::into));
212        self
213    }
214
215    /// Add a build definition file
216    ///
217    /// Supports docker-compose.yml, docker-bake.hcl, docker-bake.json,
218    /// and custom build definition files.
219    ///
220    /// # Examples
221    ///
222    /// ```
223    /// use docker_wrapper::BakeCommand;
224    ///
225    /// let bake_cmd = BakeCommand::new()
226    ///     .file("docker-compose.yml")
227    ///     .file("custom-bake.hcl");
228    /// ```
229    #[must_use]
230    pub fn file<S: Into<String>>(mut self, file: S) -> Self {
231        self.files.push(file.into());
232        self
233    }
234
235    /// Add multiple build definition files
236    ///
237    /// # Examples
238    ///
239    /// ```
240    /// use docker_wrapper::BakeCommand;
241    ///
242    /// let bake_cmd = BakeCommand::new()
243    ///     .files(vec!["docker-compose.yml", "override.yml"]);
244    /// ```
245    #[must_use]
246    pub fn files<I, S>(mut self, files: I) -> Self
247    where
248        I: IntoIterator<Item = S>,
249        S: Into<String>,
250    {
251        self.files
252            .extend(files.into_iter().map(std::convert::Into::into));
253        self
254    }
255
256    /// Allow build to access specified resources
257    ///
258    /// Grants permission to access host resources during build.
259    ///
260    /// # Examples
261    ///
262    /// ```
263    /// use docker_wrapper::BakeCommand;
264    ///
265    /// let bake_cmd = BakeCommand::new()
266    ///     .allow("network.host")
267    ///     .allow("security.insecure");
268    /// ```
269    #[must_use]
270    pub fn allow<S: Into<String>>(mut self, resource: S) -> Self {
271        self.allow.push(resource.into());
272        self
273    }
274
275    /// Override the configured builder instance
276    ///
277    /// # Examples
278    ///
279    /// ```
280    /// use docker_wrapper::BakeCommand;
281    ///
282    /// let bake_cmd = BakeCommand::new()
283    ///     .builder("mybuilder");
284    /// ```
285    #[must_use]
286    pub fn builder<S: Into<String>>(mut self, builder: S) -> Self {
287        self.builder = Some(builder.into());
288        self
289    }
290
291    /// Set method for evaluating build
292    ///
293    /// Valid values: "build", "check", "outline", "targets"
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// use docker_wrapper::BakeCommand;
299    ///
300    /// let bake_cmd = BakeCommand::new()
301    ///     .call("check"); // Validate build configuration
302    /// ```
303    #[must_use]
304    pub fn call<S: Into<String>>(mut self, method: S) -> Self {
305        self.call = Some(method.into());
306        self
307    }
308
309    /// Enable check mode (shorthand for --call=check)
310    ///
311    /// Validates the build configuration without executing the build.
312    ///
313    /// # Examples
314    ///
315    /// ```
316    /// use docker_wrapper::BakeCommand;
317    ///
318    /// let bake_cmd = BakeCommand::new()
319    ///     .check();
320    /// ```
321    #[must_use]
322    pub fn check(mut self) -> Self {
323        self.check = true;
324        self
325    }
326
327    /// Enable debug logging
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// use docker_wrapper::BakeCommand;
333    ///
334    /// let bake_cmd = BakeCommand::new()
335    ///     .debug();
336    /// ```
337    #[must_use]
338    pub fn debug(mut self) -> Self {
339        self.debug = true;
340        self
341    }
342
343    /// List targets or variables
344    ///
345    /// # Examples
346    ///
347    /// ```
348    /// use docker_wrapper::BakeCommand;
349    ///
350    /// let bake_cmd = BakeCommand::new()
351    ///     .list("targets"); // List all available targets
352    /// ```
353    #[must_use]
354    pub fn list<S: Into<String>>(mut self, list_type: S) -> Self {
355        self.list = Some(list_type.into());
356        self
357    }
358
359    /// Load images to Docker daemon (shorthand for --set=*.output=type=docker)
360    ///
361    /// # Examples
362    ///
363    /// ```
364    /// use docker_wrapper::BakeCommand;
365    ///
366    /// let bake_cmd = BakeCommand::new()
367    ///     .load();
368    /// ```
369    #[must_use]
370    pub fn load(mut self) -> Self {
371        self.load = true;
372        self
373    }
374
375    /// Write build result metadata to a file
376    ///
377    /// # Examples
378    ///
379    /// ```
380    /// use docker_wrapper::BakeCommand;
381    ///
382    /// let bake_cmd = BakeCommand::new()
383    ///     .metadata_file("build-metadata.json");
384    /// ```
385    #[must_use]
386    pub fn metadata_file<S: Into<String>>(mut self, file: S) -> Self {
387        self.metadata_file = Some(file.into());
388        self
389    }
390
391    /// Do not use cache when building images
392    ///
393    /// # Examples
394    ///
395    /// ```
396    /// use docker_wrapper::BakeCommand;
397    ///
398    /// let bake_cmd = BakeCommand::new()
399    ///     .no_cache();
400    /// ```
401    #[must_use]
402    pub fn no_cache(mut self) -> Self {
403        self.no_cache = true;
404        self
405    }
406
407    /// Print the options without building
408    ///
409    /// # Examples
410    ///
411    /// ```
412    /// use docker_wrapper::BakeCommand;
413    ///
414    /// let bake_cmd = BakeCommand::new()
415    ///     .print();
416    /// ```
417    #[must_use]
418    pub fn print(mut self) -> Self {
419        self.print = true;
420        self
421    }
422
423    /// Set type of progress output
424    ///
425    /// Valid values: "auto", "quiet", "plain", "tty", "rawjson"
426    ///
427    /// # Examples
428    ///
429    /// ```
430    /// use docker_wrapper::BakeCommand;
431    ///
432    /// let bake_cmd = BakeCommand::new()
433    ///     .progress("plain");
434    /// ```
435    #[must_use]
436    pub fn progress<S: Into<String>>(mut self, progress_type: S) -> Self {
437        self.progress = Some(progress_type.into());
438        self
439    }
440
441    /// Set provenance attestation (shorthand for --set=*.attest=type=provenance)
442    ///
443    /// # Examples
444    ///
445    /// ```
446    /// use docker_wrapper::BakeCommand;
447    ///
448    /// let bake_cmd = BakeCommand::new()
449    ///     .provenance("mode=max");
450    /// ```
451    #[must_use]
452    pub fn provenance<S: Into<String>>(mut self, provenance: S) -> Self {
453        self.provenance = Some(provenance.into());
454        self
455    }
456
457    /// Always attempt to pull all referenced images
458    ///
459    /// # Examples
460    ///
461    /// ```
462    /// use docker_wrapper::BakeCommand;
463    ///
464    /// let bake_cmd = BakeCommand::new()
465    ///     .pull();
466    /// ```
467    #[must_use]
468    pub fn pull(mut self) -> Self {
469        self.pull = true;
470        self
471    }
472
473    /// Push images to registry (shorthand for --set=*.output=type=registry)
474    ///
475    /// # Examples
476    ///
477    /// ```
478    /// use docker_wrapper::BakeCommand;
479    ///
480    /// let bake_cmd = BakeCommand::new()
481    ///     .push();
482    /// ```
483    #[must_use]
484    pub fn push(mut self) -> Self {
485        self.push = true;
486        self
487    }
488
489    /// Set SBOM attestation (shorthand for --set=*.attest=type=sbom)
490    ///
491    /// # Examples
492    ///
493    /// ```
494    /// use docker_wrapper::BakeCommand;
495    ///
496    /// let bake_cmd = BakeCommand::new()
497    ///     .sbom("generator=docker/buildkit");
498    /// ```
499    #[must_use]
500    pub fn sbom<S: Into<String>>(mut self, sbom: S) -> Self {
501        self.sbom = Some(sbom.into());
502        self
503    }
504
505    /// Override target value (e.g., "targetpattern.key=value")
506    ///
507    /// This allows overriding any target configuration at build time.
508    ///
509    /// # Examples
510    ///
511    /// ```
512    /// use docker_wrapper::BakeCommand;
513    ///
514    /// let bake_cmd = BakeCommand::new()
515    ///     .set("web.platform", "linux/amd64,linux/arm64")
516    ///     .set("*.output", "type=registry");
517    /// ```
518    #[must_use]
519    pub fn set<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
520        self.set_values.insert(key.into(), value.into());
521        self
522    }
523
524    /// Add multiple target value overrides
525    ///
526    /// # Examples
527    ///
528    /// ```
529    /// use std::collections::HashMap;
530    /// use docker_wrapper::BakeCommand;
531    ///
532    /// let mut overrides = HashMap::new();
533    /// overrides.insert("web.platform".to_string(), "linux/amd64".to_string());
534    /// overrides.insert("api.platform".to_string(), "linux/arm64".to_string());
535    ///
536    /// let bake_cmd = BakeCommand::new()
537    ///     .set_values(overrides);
538    /// ```
539    #[must_use]
540    pub fn set_values<I, K, V>(mut self, values: I) -> Self
541    where
542        I: IntoIterator<Item = (K, V)>,
543        K: Into<String>,
544        V: Into<String>,
545    {
546        self.set_values
547            .extend(values.into_iter().map(|(k, v)| (k.into(), v.into())));
548        self
549    }
550
551    /// Build the command arguments
552    ///
553    /// This method constructs the complete argument list for the docker bake command.
554    fn build_command_args(&self) -> Vec<String> {
555        let mut args = Vec::new();
556
557        // Add file arguments
558        for file in &self.files {
559            args.push("--file".to_string());
560            args.push(file.clone());
561        }
562
563        // Add allow arguments
564        for allow in &self.allow {
565            args.push("--allow".to_string());
566            args.push(allow.clone());
567        }
568
569        // Add builder
570        if let Some(ref builder) = self.builder {
571            args.push("--builder".to_string());
572            args.push(builder.clone());
573        }
574
575        // Add call method
576        if let Some(ref call) = self.call {
577            args.push("--call".to_string());
578            args.push(call.clone());
579        }
580
581        // Add check flag
582        if self.check {
583            args.push("--check".to_string());
584        }
585
586        // Add debug flag
587        if self.debug {
588            args.push("--debug".to_string());
589        }
590
591        // Add list
592        if let Some(ref list) = self.list {
593            args.push("--list".to_string());
594            args.push(list.clone());
595        }
596
597        // Add load flag
598        if self.load {
599            args.push("--load".to_string());
600        }
601
602        // Add metadata file
603        if let Some(ref metadata_file) = self.metadata_file {
604            args.push("--metadata-file".to_string());
605            args.push(metadata_file.clone());
606        }
607
608        // Add no-cache flag
609        if self.no_cache {
610            args.push("--no-cache".to_string());
611        }
612
613        // Add print flag
614        if self.print {
615            args.push("--print".to_string());
616        }
617
618        // Add progress
619        if let Some(ref progress) = self.progress {
620            args.push("--progress".to_string());
621            args.push(progress.clone());
622        }
623
624        // Add provenance
625        if let Some(ref provenance) = self.provenance {
626            args.push("--provenance".to_string());
627            args.push(provenance.clone());
628        }
629
630        // Add pull flag
631        if self.pull {
632            args.push("--pull".to_string());
633        }
634
635        // Add push flag
636        if self.push {
637            args.push("--push".to_string());
638        }
639
640        // Add sbom
641        if let Some(ref sbom) = self.sbom {
642            args.push("--sbom".to_string());
643            args.push(sbom.clone());
644        }
645
646        // Add set values
647        for (key, value) in &self.set_values {
648            args.push("--set".to_string());
649            args.push(format!("{key}={value}"));
650        }
651
652        // Add targets at the end
653        args.extend(self.targets.clone());
654
655        args
656    }
657
658    /// Get the number of targets
659    ///
660    /// # Examples
661    ///
662    /// ```
663    /// use docker_wrapper::BakeCommand;
664    ///
665    /// let bake_cmd = BakeCommand::new()
666    ///     .target("web")
667    ///     .target("api");
668    ///
669    /// assert_eq!(bake_cmd.target_count(), 2);
670    /// ```
671    #[must_use]
672    pub fn target_count(&self) -> usize {
673        self.targets.len()
674    }
675
676    /// Get the list of targets
677    ///
678    /// # Examples
679    ///
680    /// ```
681    /// use docker_wrapper::BakeCommand;
682    ///
683    /// let bake_cmd = BakeCommand::new()
684    ///     .target("web")
685    ///     .target("api");
686    ///
687    /// let targets = bake_cmd.get_targets();
688    /// assert_eq!(targets, &["web", "api"]);
689    /// ```
690    #[must_use]
691    pub fn get_targets(&self) -> &[String] {
692        &self.targets
693    }
694
695    /// Get the list of build definition files
696    ///
697    /// # Examples
698    ///
699    /// ```
700    /// use docker_wrapper::BakeCommand;
701    ///
702    /// let bake_cmd = BakeCommand::new()
703    ///     .file("docker-compose.yml");
704    ///
705    /// let files = bake_cmd.get_files();
706    /// assert_eq!(files, &["docker-compose.yml"]);
707    /// ```
708    #[must_use]
709    pub fn get_files(&self) -> &[String] {
710        &self.files
711    }
712
713    /// Check if push mode is enabled
714    ///
715    /// # Examples
716    ///
717    /// ```
718    /// use docker_wrapper::BakeCommand;
719    ///
720    /// let bake_cmd = BakeCommand::new().push();
721    /// assert!(bake_cmd.is_push_enabled());
722    /// ```
723    #[must_use]
724    pub fn is_push_enabled(&self) -> bool {
725        self.push
726    }
727
728    /// Check if load mode is enabled
729    ///
730    /// # Examples
731    ///
732    /// ```
733    /// use docker_wrapper::BakeCommand;
734    ///
735    /// let bake_cmd = BakeCommand::new().load();
736    /// assert!(bake_cmd.is_load_enabled());
737    /// ```
738    #[must_use]
739    pub fn is_load_enabled(&self) -> bool {
740        self.load
741    }
742
743    /// Check if dry-run mode is enabled (print without building)
744    ///
745    /// # Examples
746    ///
747    /// ```
748    /// use docker_wrapper::BakeCommand;
749    ///
750    /// let bake_cmd = BakeCommand::new().print();
751    /// assert!(bake_cmd.is_dry_run());
752    /// ```
753    #[must_use]
754    pub fn is_dry_run(&self) -> bool {
755        self.print
756    }
757}
758
759impl Default for BakeCommand {
760    fn default() -> Self {
761        Self::new()
762    }
763}
764
765#[async_trait]
766impl DockerCommand for BakeCommand {
767    type Output = CommandOutput;
768
769    fn command_name(&self) -> &'static str {
770        "bake"
771    }
772
773    fn build_args(&self) -> Vec<String> {
774        self.build_command_args()
775    }
776
777    async fn execute(&self) -> Result<Self::Output> {
778        let args = self.build_args();
779        self.executor
780            .execute_command(self.command_name(), args)
781            .await
782    }
783
784    fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
785        self.executor.add_arg(arg);
786        self
787    }
788
789    fn args<I, S>(&mut self, args: I) -> &mut Self
790    where
791        I: IntoIterator<Item = S>,
792        S: AsRef<OsStr>,
793    {
794        self.executor.add_args(args);
795        self
796    }
797
798    fn flag(&mut self, flag: &str) -> &mut Self {
799        self.executor.add_flag(flag);
800        self
801    }
802
803    fn option(&mut self, key: &str, value: &str) -> &mut Self {
804        self.executor.add_option(key, value);
805        self
806    }
807}
808
809#[cfg(test)]
810mod tests {
811    use super::*;
812
813    #[test]
814    fn test_bake_command_basic() {
815        let bake_cmd = BakeCommand::new();
816        let args = bake_cmd.build_args();
817
818        assert!(args.is_empty()); // No arguments for basic bake command
819    }
820
821    #[test]
822    fn test_bake_command_with_targets() {
823        let bake_cmd = BakeCommand::new()
824            .target("web")
825            .target("api")
826            .target("worker");
827
828        let args = bake_cmd.build_args();
829
830        assert!(args.contains(&"web".to_string()));
831        assert!(args.contains(&"api".to_string()));
832        assert!(args.contains(&"worker".to_string()));
833        assert_eq!(bake_cmd.target_count(), 3);
834    }
835
836    #[test]
837    fn test_bake_command_with_files() {
838        let bake_cmd = BakeCommand::new()
839            .file("docker-compose.yml")
840            .file("custom-bake.hcl");
841
842        let args = bake_cmd.build_args();
843
844        assert!(args.contains(&"--file".to_string()));
845        assert!(args.contains(&"docker-compose.yml".to_string()));
846        assert!(args.contains(&"custom-bake.hcl".to_string()));
847        assert_eq!(bake_cmd.get_files().len(), 2);
848    }
849
850    #[test]
851    fn test_bake_command_push_and_load() {
852        let bake_cmd = BakeCommand::new().push().load();
853
854        let args = bake_cmd.build_args();
855
856        assert!(args.contains(&"--push".to_string()));
857        assert!(args.contains(&"--load".to_string()));
858        assert!(bake_cmd.is_push_enabled());
859        assert!(bake_cmd.is_load_enabled());
860    }
861
862    #[test]
863    fn test_bake_command_with_builder() {
864        let bake_cmd = BakeCommand::new().builder("mybuilder");
865
866        let args = bake_cmd.build_args();
867
868        assert!(args.contains(&"--builder".to_string()));
869        assert!(args.contains(&"mybuilder".to_string()));
870    }
871
872    #[test]
873    fn test_bake_command_with_set_values() {
874        let bake_cmd = BakeCommand::new()
875            .set("web.platform", "linux/amd64,linux/arm64")
876            .set("*.output", "type=registry");
877
878        let args = bake_cmd.build_args();
879
880        assert!(args.contains(&"--set".to_string()));
881        assert!(args.contains(&"web.platform=linux/amd64,linux/arm64".to_string()));
882        assert!(args.contains(&"*.output=type=registry".to_string()));
883    }
884
885    #[test]
886    fn test_bake_command_flags() {
887        let bake_cmd = BakeCommand::new().check().debug().no_cache().print().pull();
888
889        let args = bake_cmd.build_args();
890
891        assert!(args.contains(&"--check".to_string()));
892        assert!(args.contains(&"--debug".to_string()));
893        assert!(args.contains(&"--no-cache".to_string()));
894        assert!(args.contains(&"--print".to_string()));
895        assert!(args.contains(&"--pull".to_string()));
896        assert!(bake_cmd.is_dry_run());
897    }
898
899    #[test]
900    fn test_bake_command_with_metadata_file() {
901        let bake_cmd = BakeCommand::new().metadata_file("build-metadata.json");
902
903        let args = bake_cmd.build_args();
904
905        assert!(args.contains(&"--metadata-file".to_string()));
906        assert!(args.contains(&"build-metadata.json".to_string()));
907    }
908
909    #[test]
910    fn test_bake_command_with_progress() {
911        let bake_cmd = BakeCommand::new().progress("plain");
912
913        let args = bake_cmd.build_args();
914
915        assert!(args.contains(&"--progress".to_string()));
916        assert!(args.contains(&"plain".to_string()));
917    }
918
919    #[test]
920    fn test_bake_command_with_attestations() {
921        let bake_cmd = BakeCommand::new()
922            .provenance("mode=max")
923            .sbom("generator=docker/buildkit");
924
925        let args = bake_cmd.build_args();
926
927        assert!(args.contains(&"--provenance".to_string()));
928        assert!(args.contains(&"mode=max".to_string()));
929        assert!(args.contains(&"--sbom".to_string()));
930        assert!(args.contains(&"generator=docker/buildkit".to_string()));
931    }
932
933    #[test]
934    fn test_bake_command_with_allow() {
935        let bake_cmd = BakeCommand::new()
936            .allow("network.host")
937            .allow("security.insecure");
938
939        let args = bake_cmd.build_args();
940
941        assert!(args.contains(&"--allow".to_string()));
942        assert!(args.contains(&"network.host".to_string()));
943        assert!(args.contains(&"security.insecure".to_string()));
944    }
945
946    #[test]
947    fn test_bake_command_with_call() {
948        let bake_cmd = BakeCommand::new().call("check");
949
950        let args = bake_cmd.build_args();
951
952        assert!(args.contains(&"--call".to_string()));
953        assert!(args.contains(&"check".to_string()));
954    }
955
956    #[test]
957    fn test_bake_command_with_list() {
958        let bake_cmd = BakeCommand::new().list("targets");
959
960        let args = bake_cmd.build_args();
961
962        assert!(args.contains(&"--list".to_string()));
963        assert!(args.contains(&"targets".to_string()));
964    }
965
966    #[test]
967    fn test_bake_command_extensibility() {
968        let mut bake_cmd = BakeCommand::new();
969        bake_cmd
970            .arg("--experimental")
971            .args(vec!["--custom", "value"]);
972
973        // Extensibility is handled through the executor's raw_args
974        // The actual testing of raw args is done in command.rs tests
975        // We can't access private fields, but we know the methods work
976        println!("Extensibility methods called successfully");
977    }
978}