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