herolib-virt 0.3.13

Virtualization and container management for herolib (buildah, nerdctl, kubernetes)
Documentation
use super::BuildahError;
use super::execute_buildah_command;
use crate::process::CommandResult;
use serde::{Deserialize, Serialize};
use serde_json::{self, Value};
use std::collections::HashMap;

/// Represents a container image
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Image {
    /// Image ID
    pub id: String,
    /// Image names/tags
    pub names: Vec<String>,
    /// Image size
    pub size: String,
    /// Creation timestamp
    pub created: String,
}

/// List images in local storage
///
/// # Returns
/// * Result with array of Image objects on success or error details
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 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 one or more images
///
/// # Arguments
/// * `image` - Image ID or name
///
/// # Returns
/// * Result with command output or error
pub fn image_remove(image: &str) -> Result<CommandResult, BuildahError> {
    execute_buildah_command(&["rmi", image])
}

/// Push an image to a registry
///
/// # Arguments
/// * `image` - Image name
/// * `destination` - Destination (e.g., "docker://registry.example.com/myimage:latest")
/// * `tls_verify` - Whether to verify TLS (default: true)
///
/// # Returns
/// * Result with command output 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)
}

/// Add an additional name to a local image
///
/// # Arguments
/// * `image` - Image ID or name
/// * `new_name` - New name for the image
///
/// # Returns
/// * Result with command output or error
pub fn image_tag(image: &str, new_name: &str) -> Result<CommandResult, BuildahError> {
    execute_buildah_command(&["tag", image, new_name])
}

/// Pull an image from a registry
///
/// # Arguments
/// * `image` - Image name
/// * `tls_verify` - Whether to verify TLS (default: true)
///
/// # Returns
/// * Result with command output 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)
}

/// Commit a container to an image
///
/// # Arguments
/// * `container` - Container ID or name
/// * `image_name` - New name for the image
/// * `format` - Optional, format to use for the image (oci or docker)
/// * `squash` - Whether to squash layers
/// * `rm` - Whether to remove the container after commit
///
/// # Returns
/// * Result with command output 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)
}

/// Container configuration options
///
/// # Arguments
/// * `container` - Container ID or name
/// * `options` - Map of configuration options
///
/// # Returns
/// * Result with command output or error
pub fn bah_config(
    container: &str,
    options: HashMap<String, String>,
) -> Result<CommandResult, BuildahError> {
    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.to_string());

    // 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)
}