use crate::buildah::{ execute_buildah_command, set_thread_local_debug, thread_local_debug, BuildahError, Image, }; use sal_process::CommandResult; use std::collections::HashMap; /// Builder struct for buildah operations #[derive(Clone)] pub struct Builder { /// Name of the container name: String, /// Container ID container_id: Option, /// Base image image: String, /// Debug mode debug: bool, } impl Builder { /// Create a new builder with a container from the specified image /// /// # Arguments /// /// * `name` - Name for the container /// * `image` - Image to create the container from /// /// # Returns /// /// * `Result` - Builder instance or error pub fn new(name: &str, image: &str) -> Result { // Try to create a new container let result = execute_buildah_command(&["from", "--name", name, image]); match result { Ok(success_result) => { // Container created successfully let container_id = success_result.stdout.trim().to_string(); Ok(Self { name: name.to_string(), container_id: Some(container_id), image: image.to_string(), debug: false, }) } Err(BuildahError::CommandFailed(error_msg)) => { // Check if the error is because the container already exists if error_msg.contains("that name is already in use") { // Extract the container ID from the error message // 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" let container_id = error_msg .split("already in use by ") .nth(1) .and_then(|s| s.split('.').next()) .unwrap_or("") .trim() .to_string(); if !container_id.is_empty() { // Container already exists, continue with it Ok(Self { name: name.to_string(), container_id: Some(container_id), image: image.to_string(), debug: false, }) } else { // Couldn't extract container ID Err(BuildahError::Other( "Failed to extract container ID from error message".to_string(), )) } } else { // Other command failure Err(BuildahError::CommandFailed(error_msg)) } } Err(e) => { // Other error Err(e) } } } /// Get the container ID pub fn container_id(&self) -> Option<&String> { self.container_id.as_ref() } /// Get the container name pub fn name(&self) -> &str { &self.name } /// Get the debug mode pub fn debug(&self) -> bool { self.debug } /// Set the debug mode pub fn set_debug(&mut self, debug: bool) -> &mut Self { self.debug = debug; self } /// Get the base image pub fn image(&self) -> &str { &self.image } /// Run a command in the container /// /// # Arguments /// /// * `command` - The command to run /// /// # Returns /// /// * `Result` - Command result or error pub fn run(&self, command: &str) -> Result { if let Some(container_id) = &self.container_id { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag from the Builder's debug flag set_thread_local_debug(self.debug); // Execute the command let result = execute_buildah_command(&["run", container_id, "sh", "-c", command]); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } else { Err(BuildahError::Other("No container ID available".to_string())) } } /// Run a command in the container with specified isolation /// /// # Arguments /// /// * `command` - The command to run /// * `isolation` - Isolation method (e.g., "chroot", "rootless", "oci") /// /// # Returns /// /// * `Result` - Command result or error pub fn run_with_isolation( &self, command: &str, isolation: &str, ) -> Result { if let Some(container_id) = &self.container_id { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag from the Builder's debug flag set_thread_local_debug(self.debug); // Execute the command let result = execute_buildah_command(&[ "run", "--isolation", isolation, container_id, "sh", "-c", command, ]); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } else { Err(BuildahError::Other("No container ID available".to_string())) } } /// Copy files into the container /// /// # Arguments /// /// * `source` - Source path /// * `dest` - Destination path in the container /// /// # Returns /// /// * `Result` - Command result or error pub fn copy(&self, source: &str, dest: &str) -> Result { if let Some(container_id) = &self.container_id { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag from the Builder's debug flag set_thread_local_debug(self.debug); // Execute the command let result = execute_buildah_command(&["copy", container_id, source, dest]); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } else { Err(BuildahError::Other("No container ID available".to_string())) } } /// Add files into the container /// /// # Arguments /// /// * `source` - Source path /// * `dest` - Destination path in the container /// /// # Returns /// /// * `Result` - Command result or error pub fn add(&self, source: &str, dest: &str) -> Result { if let Some(container_id) = &self.container_id { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag from the Builder's debug flag set_thread_local_debug(self.debug); // Execute the command let result = execute_buildah_command(&["add", container_id, source, dest]); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } else { Err(BuildahError::Other("No container ID available".to_string())) } } /// Commit the container to an image /// /// # Arguments /// /// * `image_name` - Name for the new image /// /// # Returns /// /// * `Result` - Command result or error pub fn commit(&self, image_name: &str) -> Result { if let Some(container_id) = &self.container_id { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag from the Builder's debug flag set_thread_local_debug(self.debug); // Execute the command let result = execute_buildah_command(&["commit", container_id, image_name]); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } else { Err(BuildahError::Other("No container ID available".to_string())) } } /// Remove the container /// /// # Returns /// /// * `Result` - Command result or error pub fn remove(&self) -> Result { if let Some(container_id) = &self.container_id { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag from the Builder's debug flag set_thread_local_debug(self.debug); // Execute the command let result = execute_buildah_command(&["rm", container_id]); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } else { Err(BuildahError::Other("No container ID available".to_string())) } } /// Reset the builder by removing the container and clearing the container_id /// /// # Returns /// /// * `Result<(), BuildahError>` - Success or error pub fn reset(&mut self) -> Result<(), BuildahError> { if let Some(container_id) = &self.container_id { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag from the Builder's debug flag set_thread_local_debug(self.debug); // Try to remove the container let result = execute_buildah_command(&["rm", container_id]); // Restore the previous debug flag set_thread_local_debug(previous_debug); // Clear the container_id regardless of whether the removal succeeded self.container_id = None; // Return the result of the removal operation match result { Ok(_) => Ok(()), Err(e) => Err(e), } } else { // No container to remove Ok(()) } } /// Configure container metadata /// /// # Arguments /// /// * `options` - Map of configuration options /// /// # Returns /// /// * `Result` - Command result or error pub fn config(&self, options: HashMap) -> Result { if let Some(container_id) = &self.container_id { let mut args_owned: Vec = Vec::new(); args_owned.push("config".to_string()); // Process options map for (key, value) in options.iter() { let option_name = format!("--{}", key); args_owned.push(option_name); args_owned.push(value.clone()); } args_owned.push(container_id.clone()); // Convert Vec to Vec<&str> for execute_buildah_command let args: Vec<&str> = args_owned.iter().map(|s| s.as_str()).collect(); // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag from the Builder's debug flag set_thread_local_debug(self.debug); // Execute the command let result = execute_buildah_command(&args); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } else { Err(BuildahError::Other("No container ID available".to_string())) } } /// Set the entrypoint for the container /// /// # Arguments /// /// * `entrypoint` - The entrypoint command /// /// # Returns /// /// * `Result` - Command result or error pub fn set_entrypoint(&self, entrypoint: &str) -> Result { if let Some(container_id) = &self.container_id { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag from the Builder's debug flag set_thread_local_debug(self.debug); // Execute the command let result = execute_buildah_command(&["config", "--entrypoint", entrypoint, container_id]); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } else { Err(BuildahError::Other("No container ID available".to_string())) } } /// Set the default command for the container /// /// # Arguments /// /// * `cmd` - The default command /// /// # Returns /// /// * `Result` - Command result or error pub fn set_cmd(&self, cmd: &str) -> Result { if let Some(container_id) = &self.container_id { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag from the Builder's debug flag set_thread_local_debug(self.debug); // Execute the command let result = execute_buildah_command(&["config", "--cmd", cmd, container_id]); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } else { Err(BuildahError::Other("No container ID available".to_string())) } } /// List images in local storage /// /// # Returns /// /// * `Result, BuildahError>` - List of images or error pub fn images() -> Result, BuildahError> { // Use default debug value (false) for static method let result = execute_buildah_command(&["images", "--json"])?; // Try to parse the JSON output match serde_json::from_str::(&result.stdout) { Ok(json) => { if let serde_json::Value::Array(images_json) = json { let mut images = Vec::new(); for image_json in images_json { // Extract image ID let id = match image_json.get("id").and_then(|v| v.as_str()) { Some(id) => id.to_string(), None => { return Err(BuildahError::ConversionError( "Missing image ID".to_string(), )) } }; // Extract image names let names = match image_json.get("names").and_then(|v| v.as_array()) { Some(names_array) => { let mut names_vec = Vec::new(); for name_value in names_array { if let Some(name_str) = name_value.as_str() { names_vec.push(name_str.to_string()); } } names_vec } None => Vec::new(), // Empty vector if no names found }; // Extract image size let size = match image_json.get("size").and_then(|v| v.as_str()) { Some(size) => size.to_string(), None => "Unknown".to_string(), // Default value if size not found }; // Extract creation timestamp let created = match image_json.get("created").and_then(|v| v.as_str()) { Some(created) => created.to_string(), None => "Unknown".to_string(), // Default value if created not found }; // Create Image struct and add to vector images.push(Image { id, names, size, created, }); } Ok(images) } else { Err(BuildahError::JsonParseError( "Expected JSON array".to_string(), )) } } Err(e) => Err(BuildahError::JsonParseError(format!( "Failed to parse image list JSON: {}", e ))), } } /// Remove an image /// /// # Arguments /// /// * `image` - Image ID or name /// /// # Returns /// /// * `Result` - Command result or error pub fn image_remove(image: &str) -> Result { // Use default debug value (false) for static method execute_buildah_command(&["rmi", image]) } /// Remove an image with debug output /// /// # Arguments /// /// * `image` - Image ID or name /// * `debug` - Whether to enable debug output /// /// # Returns /// /// * `Result` - Command result or error pub fn image_remove_with_debug( image: &str, debug: bool, ) -> Result { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag set_thread_local_debug(debug); // Execute the command let result = execute_buildah_command(&["rmi", image]); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } /// Pull an image from a registry /// /// # Arguments /// /// * `image` - Image name /// * `tls_verify` - Whether to verify TLS /// /// # Returns /// /// * `Result` - Command result or error pub fn image_pull(image: &str, tls_verify: bool) -> Result { // Use default debug value (false) for static method let mut args = vec!["pull"]; if !tls_verify { args.push("--tls-verify=false"); } args.push(image); execute_buildah_command(&args) } /// Pull an image from a registry with debug output /// /// # Arguments /// /// * `image` - Image name /// * `tls_verify` - Whether to verify TLS /// * `debug` - Whether to enable debug output /// /// # Returns /// /// * `Result` - Command result or error pub fn image_pull_with_debug( image: &str, tls_verify: bool, debug: bool, ) -> Result { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag set_thread_local_debug(debug); let mut args = vec!["pull"]; if !tls_verify { args.push("--tls-verify=false"); } args.push(image); // Execute the command let result = execute_buildah_command(&args); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } /// Push an image to a registry /// /// # Arguments /// /// * `image` - Image name /// * `destination` - Destination registry /// * `tls_verify` - Whether to verify TLS /// /// # Returns /// /// * `Result` - Command result or error pub fn image_push( image: &str, destination: &str, tls_verify: bool, ) -> Result { // Use default debug value (false) for static method let mut args = vec!["push"]; if !tls_verify { args.push("--tls-verify=false"); } args.push(image); args.push(destination); execute_buildah_command(&args) } /// Push an image to a registry with debug output /// /// # Arguments /// /// * `image` - Image name /// * `destination` - Destination registry /// * `tls_verify` - Whether to verify TLS /// * `debug` - Whether to enable debug output /// /// # Returns /// /// * `Result` - Command result or error pub fn image_push_with_debug( image: &str, destination: &str, tls_verify: bool, debug: bool, ) -> Result { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag set_thread_local_debug(debug); let mut args = vec!["push"]; if !tls_verify { args.push("--tls-verify=false"); } args.push(image); args.push(destination); // Execute the command let result = execute_buildah_command(&args); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } /// Tag an image /// /// # Arguments /// /// * `image` - Image ID or name /// * `new_name` - New tag for the image /// /// # Returns /// /// * `Result` - Command result or error pub fn image_tag(image: &str, new_name: &str) -> Result { // Use default debug value (false) for static method execute_buildah_command(&["tag", image, new_name]) } /// Tag an image with debug output /// /// # Arguments /// /// * `image` - Image ID or name /// * `new_name` - New tag for the image /// * `debug` - Whether to enable debug output /// /// # Returns /// /// * `Result` - Command result or error pub fn image_tag_with_debug( image: &str, new_name: &str, debug: bool, ) -> Result { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag set_thread_local_debug(debug); // Execute the command let result = execute_buildah_command(&["tag", image, new_name]); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } /// Commit a container to an image with advanced options /// /// # Arguments /// /// * `container` - Container ID or name /// * `image_name` - Name for the new image /// * `format` - Optional format (oci or docker) /// * `squash` - Whether to squash layers /// * `rm` - Whether to remove the container after commit /// /// # Returns /// /// * `Result` - Command result or error pub fn image_commit( container: &str, image_name: &str, format: Option<&str>, squash: bool, rm: bool, ) -> Result { // Use default debug value (false) for static method let mut args = vec!["commit"]; if let Some(format_str) = format { args.push("--format"); args.push(format_str); } if squash { args.push("--squash"); } if rm { args.push("--rm"); } args.push(container); args.push(image_name); execute_buildah_command(&args) } /// Commit a container to an image with advanced options and debug output /// /// # Arguments /// /// * `container` - Container ID or name /// * `image_name` - Name for the new image /// * `format` - Optional format (oci or docker) /// * `squash` - Whether to squash layers /// * `rm` - Whether to remove the container after commit /// * `debug` - Whether to enable debug output /// /// # Returns /// /// * `Result` - Command result or error pub fn image_commit_with_debug( container: &str, image_name: &str, format: Option<&str>, squash: bool, rm: bool, debug: bool, ) -> Result { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag set_thread_local_debug(debug); let mut args = vec!["commit"]; if let Some(format_str) = format { args.push("--format"); args.push(format_str); } if squash { args.push("--squash"); } if rm { args.push("--rm"); } args.push(container); args.push(image_name); // Execute the command let result = execute_buildah_command(&args); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } /// Build an image from a Containerfile/Dockerfile /// /// # Arguments /// /// * `tag` - Optional tag for the image /// * `context_dir` - Directory containing the Containerfile/Dockerfile /// * `file` - Path to the Containerfile/Dockerfile /// * `isolation` - Optional isolation method /// /// # Returns /// /// * `Result` - Command result or error pub fn build( tag: Option<&str>, context_dir: &str, file: &str, isolation: Option<&str>, ) -> Result { // Use default debug value (false) for static method let mut args = Vec::new(); args.push("build"); if let Some(tag_value) = tag { args.push("-t"); args.push(tag_value); } if let Some(isolation_value) = isolation { args.push("--isolation"); args.push(isolation_value); } args.push("-f"); args.push(file); args.push(context_dir); execute_buildah_command(&args) } /// Build an image from a Containerfile/Dockerfile with debug output /// /// # Arguments /// /// * `tag` - Optional tag for the image /// * `context_dir` - Directory containing the Containerfile/Dockerfile /// * `file` - Path to the Containerfile/Dockerfile /// * `isolation` - Optional isolation method /// * `debug` - Whether to enable debug output /// /// # Returns /// /// * `Result` - Command result or error pub fn build_with_debug( tag: Option<&str>, context_dir: &str, file: &str, isolation: Option<&str>, debug: bool, ) -> Result { // Save the current debug flag let previous_debug = thread_local_debug(); // Set the thread-local debug flag set_thread_local_debug(debug); let mut args = Vec::new(); args.push("build"); if let Some(tag_value) = tag { args.push("-t"); args.push(tag_value); } if let Some(isolation_value) = isolation { args.push("--isolation"); args.push(isolation_value); } args.push("-f"); args.push(file); args.push(context_dir); // Execute the command let result = execute_buildah_command(&args); // Restore the previous debug flag set_thread_local_debug(previous_debug); result } }