Skip to main content

sal_virt/buildah/
builder.rs

1use crate::buildah::{
2    execute_buildah_command, set_thread_local_debug, thread_local_debug, BuildahError, Image,
3};
4use sal_process::CommandResult;
5use std::collections::HashMap;
6
7/// Builder struct for buildah operations
8#[derive(Clone)]
9pub struct Builder {
10    /// Name of the container
11    name: String,
12    /// Container ID
13    container_id: Option<String>,
14    /// Base image
15    image: String,
16    /// Debug mode
17    debug: bool,
18}
19
20impl Builder {
21    /// Create a new builder with a container from the specified image
22    ///
23    /// # Arguments
24    ///
25    /// * `name` - Name for the container
26    /// * `image` - Image to create the container from
27    ///
28    /// # Returns
29    ///
30    /// * `Result<Self, BuildahError>` - Builder instance or error
31    pub fn new(name: &str, image: &str) -> Result<Self, BuildahError> {
32        // Try to create a new container
33        let result = execute_buildah_command(&["from", "--name", name, image]);
34
35        match result {
36            Ok(success_result) => {
37                // Container created successfully
38                let container_id = success_result.stdout.trim().to_string();
39
40                Ok(Self {
41                    name: name.to_string(),
42                    container_id: Some(container_id),
43                    image: image.to_string(),
44                    debug: false,
45                })
46            }
47            Err(BuildahError::CommandFailed(error_msg)) => {
48                // Check if the error is because the container already exists
49                if error_msg.contains("that name is already in use") {
50                    // Extract the container ID from the error message
51                    // Error format: "the container name "name" is already in use by container_id. You have to remove that container to be able to reuse that name: that name is already in use"
52                    let container_id = error_msg
53                        .split("already in use by ")
54                        .nth(1)
55                        .and_then(|s| s.split('.').next())
56                        .unwrap_or("")
57                        .trim()
58                        .to_string();
59
60                    if !container_id.is_empty() {
61                        // Container already exists, continue with it
62                        Ok(Self {
63                            name: name.to_string(),
64                            container_id: Some(container_id),
65                            image: image.to_string(),
66                            debug: false,
67                        })
68                    } else {
69                        // Couldn't extract container ID
70                        Err(BuildahError::Other(
71                            "Failed to extract container ID from error message".to_string(),
72                        ))
73                    }
74                } else {
75                    // Other command failure
76                    Err(BuildahError::CommandFailed(error_msg))
77                }
78            }
79            Err(e) => {
80                // Other error
81                Err(e)
82            }
83        }
84    }
85
86    /// Get the container ID
87    pub fn container_id(&self) -> Option<&String> {
88        self.container_id.as_ref()
89    }
90
91    /// Get the container name
92    pub fn name(&self) -> &str {
93        &self.name
94    }
95
96    /// Get the debug mode
97    pub fn debug(&self) -> bool {
98        self.debug
99    }
100
101    /// Set the debug mode
102    pub fn set_debug(&mut self, debug: bool) -> &mut Self {
103        self.debug = debug;
104        self
105    }
106
107    /// Get the base image
108    pub fn image(&self) -> &str {
109        &self.image
110    }
111
112    /// Run a command in the container
113    ///
114    /// # Arguments
115    ///
116    /// * `command` - The command to run
117    ///
118    /// # Returns
119    ///
120    /// * `Result<CommandResult, BuildahError>` - Command result or error
121    pub fn run(&self, command: &str) -> Result<CommandResult, BuildahError> {
122        if let Some(container_id) = &self.container_id {
123            // Save the current debug flag
124            let previous_debug = thread_local_debug();
125
126            // Set the thread-local debug flag from the Builder's debug flag
127            set_thread_local_debug(self.debug);
128
129            // Execute the command
130            let result = execute_buildah_command(&["run", container_id, "sh", "-c", command]);
131
132            // Restore the previous debug flag
133            set_thread_local_debug(previous_debug);
134
135            result
136        } else {
137            Err(BuildahError::Other("No container ID available".to_string()))
138        }
139    }
140
141    /// Run a command in the container with specified isolation
142    ///
143    /// # Arguments
144    ///
145    /// * `command` - The command to run
146    /// * `isolation` - Isolation method (e.g., "chroot", "rootless", "oci")
147    ///
148    /// # Returns
149    ///
150    /// * `Result<CommandResult, BuildahError>` - Command result or error
151    pub fn run_with_isolation(
152        &self,
153        command: &str,
154        isolation: &str,
155    ) -> Result<CommandResult, BuildahError> {
156        if let Some(container_id) = &self.container_id {
157            // Save the current debug flag
158            let previous_debug = thread_local_debug();
159
160            // Set the thread-local debug flag from the Builder's debug flag
161            set_thread_local_debug(self.debug);
162
163            // Execute the command
164            let result = execute_buildah_command(&[
165                "run",
166                "--isolation",
167                isolation,
168                container_id,
169                "sh",
170                "-c",
171                command,
172            ]);
173
174            // Restore the previous debug flag
175            set_thread_local_debug(previous_debug);
176
177            result
178        } else {
179            Err(BuildahError::Other("No container ID available".to_string()))
180        }
181    }
182
183    /// Copy files into the container
184    ///
185    /// # Arguments
186    ///
187    /// * `source` - Source path
188    /// * `dest` - Destination path in the container
189    ///
190    /// # Returns
191    ///
192    /// * `Result<CommandResult, BuildahError>` - Command result or error
193    pub fn copy(&self, source: &str, dest: &str) -> Result<CommandResult, BuildahError> {
194        if let Some(container_id) = &self.container_id {
195            // Save the current debug flag
196            let previous_debug = thread_local_debug();
197
198            // Set the thread-local debug flag from the Builder's debug flag
199            set_thread_local_debug(self.debug);
200
201            // Execute the command
202            let result = execute_buildah_command(&["copy", container_id, source, dest]);
203
204            // Restore the previous debug flag
205            set_thread_local_debug(previous_debug);
206
207            result
208        } else {
209            Err(BuildahError::Other("No container ID available".to_string()))
210        }
211    }
212
213    /// Add files into the container
214    ///
215    /// # Arguments
216    ///
217    /// * `source` - Source path
218    /// * `dest` - Destination path in the container
219    ///
220    /// # Returns
221    ///
222    /// * `Result<CommandResult, BuildahError>` - Command result or error
223    pub fn add(&self, source: &str, dest: &str) -> Result<CommandResult, BuildahError> {
224        if let Some(container_id) = &self.container_id {
225            // Save the current debug flag
226            let previous_debug = thread_local_debug();
227
228            // Set the thread-local debug flag from the Builder's debug flag
229            set_thread_local_debug(self.debug);
230
231            // Execute the command
232            let result = execute_buildah_command(&["add", container_id, source, dest]);
233
234            // Restore the previous debug flag
235            set_thread_local_debug(previous_debug);
236
237            result
238        } else {
239            Err(BuildahError::Other("No container ID available".to_string()))
240        }
241    }
242
243    /// Commit the container to an image
244    ///
245    /// # Arguments
246    ///
247    /// * `image_name` - Name for the new image
248    ///
249    /// # Returns
250    ///
251    /// * `Result<CommandResult, BuildahError>` - Command result or error
252    pub fn commit(&self, image_name: &str) -> Result<CommandResult, BuildahError> {
253        if let Some(container_id) = &self.container_id {
254            // Save the current debug flag
255            let previous_debug = thread_local_debug();
256
257            // Set the thread-local debug flag from the Builder's debug flag
258            set_thread_local_debug(self.debug);
259
260            // Execute the command
261            let result = execute_buildah_command(&["commit", container_id, image_name]);
262
263            // Restore the previous debug flag
264            set_thread_local_debug(previous_debug);
265
266            result
267        } else {
268            Err(BuildahError::Other("No container ID available".to_string()))
269        }
270    }
271
272    /// Remove the container
273    ///
274    /// # Returns
275    ///
276    /// * `Result<CommandResult, BuildahError>` - Command result or error
277    pub fn remove(&self) -> Result<CommandResult, BuildahError> {
278        if let Some(container_id) = &self.container_id {
279            // Save the current debug flag
280            let previous_debug = thread_local_debug();
281
282            // Set the thread-local debug flag from the Builder's debug flag
283            set_thread_local_debug(self.debug);
284
285            // Execute the command
286            let result = execute_buildah_command(&["rm", container_id]);
287
288            // Restore the previous debug flag
289            set_thread_local_debug(previous_debug);
290
291            result
292        } else {
293            Err(BuildahError::Other("No container ID available".to_string()))
294        }
295    }
296
297    /// Reset the builder by removing the container and clearing the container_id
298    ///
299    /// # Returns
300    ///
301    /// * `Result<(), BuildahError>` - Success or error
302    pub fn reset(&mut self) -> Result<(), BuildahError> {
303        if let Some(container_id) = &self.container_id {
304            // Save the current debug flag
305            let previous_debug = thread_local_debug();
306
307            // Set the thread-local debug flag from the Builder's debug flag
308            set_thread_local_debug(self.debug);
309
310            // Try to remove the container
311            let result = execute_buildah_command(&["rm", container_id]);
312
313            // Restore the previous debug flag
314            set_thread_local_debug(previous_debug);
315
316            // Clear the container_id regardless of whether the removal succeeded
317            self.container_id = None;
318
319            // Return the result of the removal operation
320            match result {
321                Ok(_) => Ok(()),
322                Err(e) => Err(e),
323            }
324        } else {
325            // No container to remove
326            Ok(())
327        }
328    }
329
330    /// Configure container metadata
331    ///
332    /// # Arguments
333    ///
334    /// * `options` - Map of configuration options
335    ///
336    /// # Returns
337    ///
338    /// * `Result<CommandResult, BuildahError>` - Command result or error
339    pub fn config(&self, options: HashMap<String, String>) -> Result<CommandResult, BuildahError> {
340        if let Some(container_id) = &self.container_id {
341            let mut args_owned: Vec<String> = Vec::new();
342            args_owned.push("config".to_string());
343
344            // Process options map
345            for (key, value) in options.iter() {
346                let option_name = format!("--{}", key);
347                args_owned.push(option_name);
348                args_owned.push(value.clone());
349            }
350
351            args_owned.push(container_id.clone());
352
353            // Convert Vec<String> to Vec<&str> for execute_buildah_command
354            let args: Vec<&str> = args_owned.iter().map(|s| s.as_str()).collect();
355
356            // Save the current debug flag
357            let previous_debug = thread_local_debug();
358
359            // Set the thread-local debug flag from the Builder's debug flag
360            set_thread_local_debug(self.debug);
361
362            // Execute the command
363            let result = execute_buildah_command(&args);
364
365            // Restore the previous debug flag
366            set_thread_local_debug(previous_debug);
367
368            result
369        } else {
370            Err(BuildahError::Other("No container ID available".to_string()))
371        }
372    }
373
374    /// Set the entrypoint for the container
375    ///
376    /// # Arguments
377    ///
378    /// * `entrypoint` - The entrypoint command
379    ///
380    /// # Returns
381    ///
382    /// * `Result<CommandResult, BuildahError>` - Command result or error
383    pub fn set_entrypoint(&self, entrypoint: &str) -> Result<CommandResult, BuildahError> {
384        if let Some(container_id) = &self.container_id {
385            // Save the current debug flag
386            let previous_debug = thread_local_debug();
387
388            // Set the thread-local debug flag from the Builder's debug flag
389            set_thread_local_debug(self.debug);
390
391            // Execute the command
392            let result =
393                execute_buildah_command(&["config", "--entrypoint", entrypoint, container_id]);
394
395            // Restore the previous debug flag
396            set_thread_local_debug(previous_debug);
397
398            result
399        } else {
400            Err(BuildahError::Other("No container ID available".to_string()))
401        }
402    }
403
404    /// Set the default command for the container
405    ///
406    /// # Arguments
407    ///
408    /// * `cmd` - The default command
409    ///
410    /// # Returns
411    ///
412    /// * `Result<CommandResult, BuildahError>` - Command result or error
413    pub fn set_cmd(&self, cmd: &str) -> Result<CommandResult, BuildahError> {
414        if let Some(container_id) = &self.container_id {
415            // Save the current debug flag
416            let previous_debug = thread_local_debug();
417
418            // Set the thread-local debug flag from the Builder's debug flag
419            set_thread_local_debug(self.debug);
420
421            // Execute the command
422            let result = execute_buildah_command(&["config", "--cmd", cmd, container_id]);
423
424            // Restore the previous debug flag
425            set_thread_local_debug(previous_debug);
426
427            result
428        } else {
429            Err(BuildahError::Other("No container ID available".to_string()))
430        }
431    }
432
433    /// List images in local storage
434    ///
435    /// # Returns
436    ///
437    /// * `Result<Vec<Image>, BuildahError>` - List of images or error
438    pub fn images() -> Result<Vec<Image>, BuildahError> {
439        // Use default debug value (false) for static method
440        let result = execute_buildah_command(&["images", "--json"])?;
441
442        // Try to parse the JSON output
443        match serde_json::from_str::<serde_json::Value>(&result.stdout) {
444            Ok(json) => {
445                if let serde_json::Value::Array(images_json) = json {
446                    let mut images = Vec::new();
447
448                    for image_json in images_json {
449                        // Extract image ID
450                        let id = match image_json.get("id").and_then(|v| v.as_str()) {
451                            Some(id) => id.to_string(),
452                            None => {
453                                return Err(BuildahError::ConversionError(
454                                    "Missing image ID".to_string(),
455                                ))
456                            }
457                        };
458
459                        // Extract image names
460                        let names = match image_json.get("names").and_then(|v| v.as_array()) {
461                            Some(names_array) => {
462                                let mut names_vec = Vec::new();
463                                for name_value in names_array {
464                                    if let Some(name_str) = name_value.as_str() {
465                                        names_vec.push(name_str.to_string());
466                                    }
467                                }
468                                names_vec
469                            }
470                            None => Vec::new(), // Empty vector if no names found
471                        };
472
473                        // Extract image size
474                        let size = match image_json.get("size").and_then(|v| v.as_str()) {
475                            Some(size) => size.to_string(),
476                            None => "Unknown".to_string(), // Default value if size not found
477                        };
478
479                        // Extract creation timestamp
480                        let created = match image_json.get("created").and_then(|v| v.as_str()) {
481                            Some(created) => created.to_string(),
482                            None => "Unknown".to_string(), // Default value if created not found
483                        };
484
485                        // Create Image struct and add to vector
486                        images.push(Image {
487                            id,
488                            names,
489                            size,
490                            created,
491                        });
492                    }
493
494                    Ok(images)
495                } else {
496                    Err(BuildahError::JsonParseError(
497                        "Expected JSON array".to_string(),
498                    ))
499                }
500            }
501            Err(e) => Err(BuildahError::JsonParseError(format!(
502                "Failed to parse image list JSON: {}",
503                e
504            ))),
505        }
506    }
507
508    /// Remove an image
509    ///
510    /// # Arguments
511    ///
512    /// * `image` - Image ID or name
513    ///
514    /// # Returns
515    ///
516    /// * `Result<CommandResult, BuildahError>` - Command result or error
517    pub fn image_remove(image: &str) -> Result<CommandResult, BuildahError> {
518        // Use default debug value (false) for static method
519        execute_buildah_command(&["rmi", image])
520    }
521
522    /// Remove an image with debug output
523    ///
524    /// # Arguments
525    ///
526    /// * `image` - Image ID or name
527    /// * `debug` - Whether to enable debug output
528    ///
529    /// # Returns
530    ///
531    /// * `Result<CommandResult, BuildahError>` - Command result or error
532    pub fn image_remove_with_debug(
533        image: &str,
534        debug: bool,
535    ) -> Result<CommandResult, BuildahError> {
536        // Save the current debug flag
537        let previous_debug = thread_local_debug();
538
539        // Set the thread-local debug flag
540        set_thread_local_debug(debug);
541
542        // Execute the command
543        let result = execute_buildah_command(&["rmi", image]);
544
545        // Restore the previous debug flag
546        set_thread_local_debug(previous_debug);
547
548        result
549    }
550
551    /// Pull an image from a registry
552    ///
553    /// # Arguments
554    ///
555    /// * `image` - Image name
556    /// * `tls_verify` - Whether to verify TLS
557    ///
558    /// # Returns
559    ///
560    /// * `Result<CommandResult, BuildahError>` - Command result or error
561    pub fn image_pull(image: &str, tls_verify: bool) -> Result<CommandResult, BuildahError> {
562        // Use default debug value (false) for static method
563        let mut args = vec!["pull"];
564
565        if !tls_verify {
566            args.push("--tls-verify=false");
567        }
568
569        args.push(image);
570
571        execute_buildah_command(&args)
572    }
573
574    /// Pull an image from a registry with debug output
575    ///
576    /// # Arguments
577    ///
578    /// * `image` - Image name
579    /// * `tls_verify` - Whether to verify TLS
580    /// * `debug` - Whether to enable debug output
581    ///
582    /// # Returns
583    ///
584    /// * `Result<CommandResult, BuildahError>` - Command result or error
585    pub fn image_pull_with_debug(
586        image: &str,
587        tls_verify: bool,
588        debug: bool,
589    ) -> Result<CommandResult, BuildahError> {
590        // Save the current debug flag
591        let previous_debug = thread_local_debug();
592
593        // Set the thread-local debug flag
594        set_thread_local_debug(debug);
595
596        let mut args = vec!["pull"];
597
598        if !tls_verify {
599            args.push("--tls-verify=false");
600        }
601
602        args.push(image);
603
604        // Execute the command
605        let result = execute_buildah_command(&args);
606
607        // Restore the previous debug flag
608        set_thread_local_debug(previous_debug);
609
610        result
611    }
612
613    /// Push an image to a registry
614    ///
615    /// # Arguments
616    ///
617    /// * `image` - Image name
618    /// * `destination` - Destination registry
619    /// * `tls_verify` - Whether to verify TLS
620    ///
621    /// # Returns
622    ///
623    /// * `Result<CommandResult, BuildahError>` - Command result or error
624    pub fn image_push(
625        image: &str,
626        destination: &str,
627        tls_verify: bool,
628    ) -> Result<CommandResult, BuildahError> {
629        // Use default debug value (false) for static method
630        let mut args = vec!["push"];
631
632        if !tls_verify {
633            args.push("--tls-verify=false");
634        }
635
636        args.push(image);
637        args.push(destination);
638
639        execute_buildah_command(&args)
640    }
641
642    /// Push an image to a registry with debug output
643    ///
644    /// # Arguments
645    ///
646    /// * `image` - Image name
647    /// * `destination` - Destination registry
648    /// * `tls_verify` - Whether to verify TLS
649    /// * `debug` - Whether to enable debug output
650    ///
651    /// # Returns
652    ///
653    /// * `Result<CommandResult, BuildahError>` - Command result or error
654    pub fn image_push_with_debug(
655        image: &str,
656        destination: &str,
657        tls_verify: bool,
658        debug: bool,
659    ) -> Result<CommandResult, BuildahError> {
660        // Save the current debug flag
661        let previous_debug = thread_local_debug();
662
663        // Set the thread-local debug flag
664        set_thread_local_debug(debug);
665
666        let mut args = vec!["push"];
667
668        if !tls_verify {
669            args.push("--tls-verify=false");
670        }
671
672        args.push(image);
673        args.push(destination);
674
675        // Execute the command
676        let result = execute_buildah_command(&args);
677
678        // Restore the previous debug flag
679        set_thread_local_debug(previous_debug);
680
681        result
682    }
683
684    /// Tag an image
685    ///
686    /// # Arguments
687    ///
688    /// * `image` - Image ID or name
689    /// * `new_name` - New tag for the image
690    ///
691    /// # Returns
692    ///
693    /// * `Result<CommandResult, BuildahError>` - Command result or error
694    pub fn image_tag(image: &str, new_name: &str) -> Result<CommandResult, BuildahError> {
695        // Use default debug value (false) for static method
696        execute_buildah_command(&["tag", image, new_name])
697    }
698
699    /// Tag an image with debug output
700    ///
701    /// # Arguments
702    ///
703    /// * `image` - Image ID or name
704    /// * `new_name` - New tag for the image
705    /// * `debug` - Whether to enable debug output
706    ///
707    /// # Returns
708    ///
709    /// * `Result<CommandResult, BuildahError>` - Command result or error
710    pub fn image_tag_with_debug(
711        image: &str,
712        new_name: &str,
713        debug: bool,
714    ) -> Result<CommandResult, BuildahError> {
715        // Save the current debug flag
716        let previous_debug = thread_local_debug();
717
718        // Set the thread-local debug flag
719        set_thread_local_debug(debug);
720
721        // Execute the command
722        let result = execute_buildah_command(&["tag", image, new_name]);
723
724        // Restore the previous debug flag
725        set_thread_local_debug(previous_debug);
726
727        result
728    }
729
730    /// Commit a container to an image with advanced options
731    ///
732    /// # Arguments
733    ///
734    /// * `container` - Container ID or name
735    /// * `image_name` - Name for the new image
736    /// * `format` - Optional format (oci or docker)
737    /// * `squash` - Whether to squash layers
738    /// * `rm` - Whether to remove the container after commit
739    ///
740    /// # Returns
741    ///
742    /// * `Result<CommandResult, BuildahError>` - Command result or error
743    pub fn image_commit(
744        container: &str,
745        image_name: &str,
746        format: Option<&str>,
747        squash: bool,
748        rm: bool,
749    ) -> Result<CommandResult, BuildahError> {
750        // Use default debug value (false) for static method
751        let mut args = vec!["commit"];
752
753        if let Some(format_str) = format {
754            args.push("--format");
755            args.push(format_str);
756        }
757
758        if squash {
759            args.push("--squash");
760        }
761
762        if rm {
763            args.push("--rm");
764        }
765
766        args.push(container);
767        args.push(image_name);
768
769        execute_buildah_command(&args)
770    }
771
772    /// Commit a container to an image with advanced options and debug output
773    ///
774    /// # Arguments
775    ///
776    /// * `container` - Container ID or name
777    /// * `image_name` - Name for the new image
778    /// * `format` - Optional format (oci or docker)
779    /// * `squash` - Whether to squash layers
780    /// * `rm` - Whether to remove the container after commit
781    /// * `debug` - Whether to enable debug output
782    ///
783    /// # Returns
784    ///
785    /// * `Result<CommandResult, BuildahError>` - Command result or error
786    pub fn image_commit_with_debug(
787        container: &str,
788        image_name: &str,
789        format: Option<&str>,
790        squash: bool,
791        rm: bool,
792        debug: bool,
793    ) -> Result<CommandResult, BuildahError> {
794        // Save the current debug flag
795        let previous_debug = thread_local_debug();
796
797        // Set the thread-local debug flag
798        set_thread_local_debug(debug);
799
800        let mut args = vec!["commit"];
801
802        if let Some(format_str) = format {
803            args.push("--format");
804            args.push(format_str);
805        }
806
807        if squash {
808            args.push("--squash");
809        }
810
811        if rm {
812            args.push("--rm");
813        }
814
815        args.push(container);
816        args.push(image_name);
817
818        // Execute the command
819        let result = execute_buildah_command(&args);
820
821        // Restore the previous debug flag
822        set_thread_local_debug(previous_debug);
823
824        result
825    }
826
827    /// Build an image from a Containerfile/Dockerfile
828    ///
829    /// # Arguments
830    ///
831    /// * `tag` - Optional tag for the image
832    /// * `context_dir` - Directory containing the Containerfile/Dockerfile
833    /// * `file` - Path to the Containerfile/Dockerfile
834    /// * `isolation` - Optional isolation method
835    ///
836    /// # Returns
837    ///
838    /// * `Result<CommandResult, BuildahError>` - Command result or error
839    pub fn build(
840        tag: Option<&str>,
841        context_dir: &str,
842        file: &str,
843        isolation: Option<&str>,
844    ) -> Result<CommandResult, BuildahError> {
845        // Use default debug value (false) for static method
846        let mut args = Vec::new();
847        args.push("build");
848
849        if let Some(tag_value) = tag {
850            args.push("-t");
851            args.push(tag_value);
852        }
853
854        if let Some(isolation_value) = isolation {
855            args.push("--isolation");
856            args.push(isolation_value);
857        }
858
859        args.push("-f");
860        args.push(file);
861
862        args.push(context_dir);
863
864        execute_buildah_command(&args)
865    }
866
867    /// Build an image from a Containerfile/Dockerfile with debug output
868    ///
869    /// # Arguments
870    ///
871    /// * `tag` - Optional tag for the image
872    /// * `context_dir` - Directory containing the Containerfile/Dockerfile
873    /// * `file` - Path to the Containerfile/Dockerfile
874    /// * `isolation` - Optional isolation method
875    /// * `debug` - Whether to enable debug output
876    ///
877    /// # Returns
878    ///
879    /// * `Result<CommandResult, BuildahError>` - Command result or error
880    pub fn build_with_debug(
881        tag: Option<&str>,
882        context_dir: &str,
883        file: &str,
884        isolation: Option<&str>,
885        debug: bool,
886    ) -> Result<CommandResult, BuildahError> {
887        // Save the current debug flag
888        let previous_debug = thread_local_debug();
889
890        // Set the thread-local debug flag
891        set_thread_local_debug(debug);
892
893        let mut args = Vec::new();
894        args.push("build");
895
896        if let Some(tag_value) = tag {
897            args.push("-t");
898            args.push(tag_value);
899        }
900
901        if let Some(isolation_value) = isolation {
902            args.push("--isolation");
903            args.push(isolation_value);
904        }
905
906        args.push("-f");
907        args.push(file);
908
909        args.push(context_dir);
910
911        // Execute the command
912        let result = execute_buildah_command(&args);
913
914        // Restore the previous debug flag
915        set_thread_local_debug(previous_debug);
916
917        result
918    }
919}