Files
herolib_rust/virt/src/buildah/builder.rs
Mahmoud-Emad 455f84528b
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
feat: Add support for virt package
- Add sal-virt package to the workspace members
- Update MONOREPO_CONVERSION_PLAN.md to reflect the
  completion of sal-process and sal-virt packages
- Update src/lib.rs to include sal-virt
- Update src/postgresclient to use sal-virt instead of local
  virt module
- Update tests to use sal-virt
2025-06-23 02:37:14 +03:00

920 lines
28 KiB
Rust

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<String>,
/// 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<Self, BuildahError>` - Builder instance or error
pub fn new(name: &str, image: &str) -> Result<Self, BuildahError> {
// 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<CommandResult, BuildahError>` - Command result or error
pub fn run(&self, command: &str) -> Result<CommandResult, 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);
// 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<CommandResult, BuildahError>` - Command result or error
pub fn run_with_isolation(
&self,
command: &str,
isolation: &str,
) -> Result<CommandResult, 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);
// 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<CommandResult, BuildahError>` - Command result or error
pub fn copy(&self, source: &str, dest: &str) -> Result<CommandResult, 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);
// 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<CommandResult, BuildahError>` - Command result or error
pub fn add(&self, source: &str, dest: &str) -> Result<CommandResult, 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);
// 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<CommandResult, BuildahError>` - Command result or error
pub fn commit(&self, image_name: &str) -> Result<CommandResult, 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);
// 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<CommandResult, BuildahError>` - Command result or error
pub fn remove(&self) -> Result<CommandResult, 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);
// 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<CommandResult, BuildahError>` - Command result or error
pub fn config(&self, options: HashMap<String, String>) -> Result<CommandResult, BuildahError> {
if let Some(container_id) = &self.container_id {
let mut args_owned: Vec<String> = 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<String> 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<CommandResult, BuildahError>` - Command result or error
pub fn set_entrypoint(&self, entrypoint: &str) -> Result<CommandResult, 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);
// 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<CommandResult, BuildahError>` - Command result or error
pub fn set_cmd(&self, cmd: &str) -> Result<CommandResult, 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);
// 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<Vec<Image>, BuildahError>` - List of images or error
pub fn images() -> Result<Vec<Image>, 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::<serde_json::Value>(&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<CommandResult, BuildahError>` - Command result or error
pub fn image_remove(image: &str) -> Result<CommandResult, BuildahError> {
// 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<CommandResult, BuildahError>` - Command result or error
pub fn image_remove_with_debug(
image: &str,
debug: bool,
) -> Result<CommandResult, BuildahError> {
// 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<CommandResult, BuildahError>` - Command result or error
pub fn image_pull(image: &str, tls_verify: bool) -> Result<CommandResult, BuildahError> {
// 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<CommandResult, BuildahError>` - Command result or error
pub fn image_pull_with_debug(
image: &str,
tls_verify: bool,
debug: bool,
) -> Result<CommandResult, BuildahError> {
// 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<CommandResult, BuildahError>` - Command result or error
pub fn image_push(
image: &str,
destination: &str,
tls_verify: bool,
) -> Result<CommandResult, BuildahError> {
// 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<CommandResult, BuildahError>` - Command result or error
pub fn image_push_with_debug(
image: &str,
destination: &str,
tls_verify: bool,
debug: bool,
) -> Result<CommandResult, BuildahError> {
// 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<CommandResult, BuildahError>` - Command result or error
pub fn image_tag(image: &str, new_name: &str) -> Result<CommandResult, BuildahError> {
// 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<CommandResult, BuildahError>` - Command result or error
pub fn image_tag_with_debug(
image: &str,
new_name: &str,
debug: bool,
) -> Result<CommandResult, BuildahError> {
// 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<CommandResult, BuildahError>` - Command result or error
pub fn image_commit(
container: &str,
image_name: &str,
format: Option<&str>,
squash: bool,
rm: bool,
) -> Result<CommandResult, BuildahError> {
// 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<CommandResult, BuildahError>` - 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<CommandResult, BuildahError> {
// 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<CommandResult, BuildahError>` - Command result or error
pub fn build(
tag: Option<&str>,
context_dir: &str,
file: &str,
isolation: Option<&str>,
) -> Result<CommandResult, BuildahError> {
// 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<CommandResult, BuildahError>` - Command result or error
pub fn build_with_debug(
tag: Option<&str>,
context_dir: &str,
file: &str,
isolation: Option<&str>,
debug: bool,
) -> Result<CommandResult, BuildahError> {
// 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
}
}