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}