...
This commit is contained in:
458
src/virt/buildah/builder.rs
Normal file
458
src/virt/buildah/builder.rs
Normal file
@@ -0,0 +1,458 @@
|
||||
use crate::process::CommandResult;
|
||||
use crate::virt::buildah::{execute_buildah_command, BuildahError, Image};
|
||||
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,
|
||||
}
|
||||
|
||||
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(),
|
||||
})
|
||||
},
|
||||
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(),
|
||||
})
|
||||
} 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 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 {
|
||||
execute_buildah_command(&["run", container_id, "sh", "-c", command])
|
||||
} 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 {
|
||||
execute_buildah_command(&["run", "--isolation", isolation, container_id, "sh", "-c", command])
|
||||
} 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 {
|
||||
execute_buildah_command(&["copy", container_id, source, dest])
|
||||
} 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 {
|
||||
execute_buildah_command(&["add", container_id, source, dest])
|
||||
} 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 {
|
||||
execute_buildah_command(&["commit", container_id, image_name])
|
||||
} 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 {
|
||||
execute_buildah_command(&["rm", container_id])
|
||||
} 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 {
|
||||
// Try to remove the container
|
||||
let result = execute_buildah_command(&["rm", container_id]);
|
||||
|
||||
// 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();
|
||||
|
||||
execute_buildah_command(&args)
|
||||
} 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> {
|
||||
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> {
|
||||
execute_buildah_command(&["rmi", image])
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let mut args = vec!["pull"];
|
||||
|
||||
if !tls_verify {
|
||||
args.push("--tls-verify=false");
|
||||
}
|
||||
|
||||
args.push(image);
|
||||
|
||||
execute_buildah_command(&args)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let mut args = vec!["push"];
|
||||
|
||||
if !tls_verify {
|
||||
args.push("--tls-verify=false");
|
||||
}
|
||||
|
||||
args.push(image);
|
||||
args.push(destination);
|
||||
|
||||
execute_buildah_command(&args)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
execute_buildah_command(&["tag", image, new_name])
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
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)
|
||||
}
|
||||
}
|
@@ -11,6 +11,7 @@ mod tests {
|
||||
lazy_static! {
|
||||
static ref LAST_COMMAND: Mutex<Vec<String>> = Mutex::new(Vec::new());
|
||||
static ref SHOULD_FAIL: Mutex<bool> = Mutex::new(false);
|
||||
static ref TEST_MUTEX: Mutex<()> = Mutex::new(()); // Add a mutex for test synchronization
|
||||
}
|
||||
|
||||
fn reset_test_state() {
|
||||
@@ -117,6 +118,7 @@ mod tests {
|
||||
// Tests for each function
|
||||
#[test]
|
||||
fn test_from_function() {
|
||||
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||
reset_test_state();
|
||||
|
||||
let image = "alpine:latest";
|
||||
@@ -129,6 +131,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_run_function() {
|
||||
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||
reset_test_state();
|
||||
|
||||
let container = "my-container";
|
||||
@@ -143,6 +146,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bah_run_with_isolation_function() {
|
||||
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||
reset_test_state();
|
||||
|
||||
let container = "my-container";
|
||||
@@ -157,6 +161,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bah_copy_function() {
|
||||
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||
reset_test_state();
|
||||
|
||||
let container = "my-container";
|
||||
@@ -171,6 +176,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bah_add_function() {
|
||||
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||
reset_test_state();
|
||||
|
||||
let container = "my-container";
|
||||
@@ -185,6 +191,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bah_commit_function() {
|
||||
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||
reset_test_state();
|
||||
|
||||
let container = "my-container";
|
||||
@@ -198,6 +205,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bah_remove_function() {
|
||||
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||
reset_test_state();
|
||||
|
||||
let container = "my-container";
|
||||
@@ -210,6 +218,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bah_list_function() {
|
||||
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||
reset_test_state();
|
||||
|
||||
let result = test_bah_list();
|
||||
@@ -221,6 +230,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bah_build_function() {
|
||||
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||
reset_test_state();
|
||||
|
||||
// Test with tag, context directory, file, and no isolation
|
||||
@@ -229,12 +239,16 @@ mod tests {
|
||||
let cmd = get_last_command();
|
||||
assert_eq!(cmd, vec!["build", "-t", "my-app:latest", "-f", "Dockerfile", "."]);
|
||||
|
||||
reset_test_state(); // Reset state between sub-tests
|
||||
|
||||
// Test with tag, context directory, file, and isolation
|
||||
let result = test_bah_build(Some("my-app:latest"), ".", "Dockerfile.custom", Some("chroot"));
|
||||
assert!(result.is_ok());
|
||||
let cmd = get_last_command();
|
||||
assert_eq!(cmd, vec!["build", "-t", "my-app:latest", "--isolation", "chroot", "-f", "Dockerfile.custom", "."]);
|
||||
|
||||
reset_test_state(); // Reset state between sub-tests
|
||||
|
||||
// Test with just context directory and file
|
||||
let result = test_bah_build(None, ".", "Dockerfile", None);
|
||||
assert!(result.is_ok());
|
||||
@@ -244,6 +258,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_error_handling() {
|
||||
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||
reset_test_state();
|
||||
set_should_fail(true);
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
mod containers;
|
||||
mod images;
|
||||
mod cmd;
|
||||
mod builder;
|
||||
#[cfg(test)]
|
||||
mod containers_test;
|
||||
|
||||
@@ -43,6 +44,12 @@ impl Error for BuildahError {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Re-export the Builder
|
||||
pub use builder::Builder;
|
||||
|
||||
// Re-export existing functions for backward compatibility
|
||||
#[deprecated(since = "0.2.0", note = "Use Builder::new() instead")]
|
||||
pub use containers::*;
|
||||
#[deprecated(since = "0.2.0", note = "Use Builder methods instead")]
|
||||
pub use images::*;
|
||||
pub use cmd::*;
|
Reference in New Issue
Block a user