herolib-virt 0.3.13

Virtualization and container management for herolib (buildah, nerdctl, kubernetes)
Documentation
//! Buildah: Container Image Building
//!
//! This module provides a comprehensive Rust interface for building OCI and Docker-compatible
//! container images using `buildah`. It offers both a high-level [`Builder`] API for step-by-step
//! image construction and static functions for managing images in local storage.
//!
//! # Features
//!
//! - Create and manage working containers from base images
//! - Execute commands inside containers
//! - Copy files and directories into containers
//! - Configure image metadata (entrypoint, cmd, env, labels, etc.)
//! - Commit containers to images
//! - Push and pull images from registries
//! - Support for remote execution via SSH or kubectl
//!
//! # Rust Example
//!
//! ```rust,no_run
//! use herolib_virt::buildah::{Builder, BuildahError};
//! use std::collections::HashMap;
//!
//! fn main() -> Result<(), BuildahError> {
//!     // Create a new builder from a base image
//!     let mut builder = Builder::new("my-container", "alpine:latest")?;
//!     builder.set_debug(true);
//!
//!     // Run commands inside the container
//!     builder.run("apk add --no-cache curl nginx")?;
//!     builder.run("mkdir -p /var/www/html")?;
//!
//!     // Configure the image
//!     let mut config = HashMap::new();
//!     config.insert("workingdir".to_string(), "/var/www/html".to_string());
//!     builder.config(config)?;
//!
//!     builder.set_entrypoint("/usr/sbin/nginx")?;
//!     builder.set_cmd("-g 'daemon off;'")?;
//!
//!     // Commit the container to an image
//!     builder.commit("my-webserver:latest")?;
//!
//!     // Clean up the working container
//!     builder.remove()?;
//!
//!     Ok(())
//! }
//! ```
//!
//! # Rhai Scripting Examples
//!
//! The Buildah module is fully accessible from Rhai scripts via `herodo`.
//!
//! ## Fluent API (Recommended)
//!
//! The `bah()` function provides a clean, chainable API for building container images:
//!
//! ```rhai
//! // Build a web server image with method chaining
//! let b = bah("webserver", "alpine:latest")
//!     .run("apk update")
//!     .run("apk add --no-cache nginx")
//!     .run("mkdir -p /var/www/html")
//!     .write("<h1>Hello World</h1>", "/var/www/html/index.html")
//!     .entrypoint("/usr/sbin/nginx")
//!     .cmd("-g 'daemon off;'")
//!     .commit("my-webserver:latest");
//!
//! // Check if commit succeeded
//! if b.success {
//!     print("Image built successfully!");
//!     print("Image ID: " + b.stdout);
//! } else {
//!     print("Build failed: " + b.stderr);
//! }
//!
//! // Clean up the container
//! b.remove();
//! ```
//!
//! ## Multi-line Content and Configuration Files
//!
//! ```rhai
//! let nginx_conf = `
//! server {
//!     listen 80;
//!     server_name localhost;
//!     root /var/www/html;
//!
//!     location / {
//!         try_files $uri $uri/ =404;
//!     }
//! }
//! `;
//!
//! let b = bah("nginx-custom", "nginx:alpine")
//!     .write(nginx_conf, "/etc/nginx/conf.d/default.conf")
//!     .write("<h1>Custom Nginx</h1>", "/var/www/html/index.html")
//!     .commit("my-nginx:latest");
//!
//! if b.success {
//!     print("Nginx image built!");
//! }
//! b.remove();
//! ```
//!
//! ## Building a Python Application
//!
//! ```rhai
//! let requirements = `
//! flask==2.3.0
//! gunicorn==21.2.0
//! requests==2.31.0
//! `;
//!
//! let app_py = `
//! from flask import Flask
//! app = Flask(__name__)
//!
//! @app.route('/')
//! def hello():
//!     return 'Hello from Flask!'
//!
//! if __name__ == '__main__':
//!     app.run(host='0.0.0.0', port=5000)
//! `;
//!
//! let b = bah("flask-app", "python:3.11-slim")
//!     .run("pip install --no-cache-dir flask gunicorn requests")
//!     .run("mkdir -p /app")
//!     .write(app_py, "/app/app.py")
//!     .config(#{ "workingdir": "/app" })
//!     .entrypoint("gunicorn")
//!     .cmd("-w 4 -b 0.0.0.0:5000 app:app")
//!     .commit("my-flask-app:latest");
//!
//! if b.success {
//!     print("Flask app image built!");
//! }
//! b.remove();
//! ```
//!
//! ## Building with Environment Variables and Labels
//!
//! ```rhai
//! let b = bah("app-with-env", "node:18-alpine")
//!     .run("npm install -g pm2")
//!     .config(#{
//!         "env": "NODE_ENV=production",
//!         "label": "maintainer=devops@example.com",
//!         "workingdir": "/app"
//!     })
//!     .entrypoint("pm2-runtime")
//!     .cmd("start ecosystem.config.js")
//!     .commit("my-node-app:latest");
//!
//! b.remove();
//! ```
//!
//! ## Reading Content from Container
//!
//! ```rhai
//! let b = bah("reader", "alpine:latest")
//!     .run("echo 'Hello from container' > /tmp/test.txt");
//!
//! // Read content from a file in the container
//! let content = b.read("/tmp/test.txt");
//! print("File content: " + content);
//!
//! b.remove();
//! ```
//!
//! ## Debug Mode
//!
//! ```rhai
//! // Enable debug mode to see buildah commands being executed
//! let b = bah("debug-container", "alpine:latest");
//! b.debug = true;  // Enable debug output
//!
//! b.run("echo 'This will show the buildah command'");
//! b.remove();
//! ```
//!
//! ## Image Management
//!
//! ```rhai
//! let b = bah("temp", "alpine:latest");
//!
//! // List all images
//! let images = b.images();
//! for img in images {
//!     print("Image: " + img.id + " - " + img.names.join(", "));
//! }
//!
//! // Pull an image
//! b.image_pull("docker.io/library/nginx:latest", true);
//!
//! // Tag an image
//! b.image_tag("nginx:latest", "my-registry/nginx:v1");
//!
//! // Push an image (requires authentication)
//! // b.image_push("my-registry/nginx:v1", "docker://my-registry/nginx:v1", true);
//!
//! // Remove an image
//! b.image_remove("my-registry/nginx:v1");
//!
//! b.remove();
//! ```
//!
//! ## Copying Files from Host
//!
//! ```rhai
//! let b = bah("copy-example", "alpine:latest")
//!     .copy("/path/to/local/file.txt", "/app/file.txt")
//!     .copy("/path/to/local/dir", "/app/dir")
//!     .commit("my-app-with-files:latest");
//!
//! b.remove();
//! ```
//!
//! ## Build from Dockerfile
//!
//! ```rhai
//! let b = bah("temp", "alpine:latest");
//!
//! // Build an image from a Dockerfile
//! let result = b.build("my-image:latest", "/path/to/context", "Dockerfile", "chroot");
//! if result.success {
//!     print("Image built from Dockerfile!");
//! }
//!
//! b.remove();
//! ```
//!
//! ## Legacy API
//!
//! The legacy `bah_new()` function is still available for backward compatibility:
//!
//! ```rhai
//! // Create a builder (legacy style)
//! let builder = bah_new("my-container", "alpine:latest");
//!
//! // Run commands
//! builder.run("apk add curl");
//!
//! // Commit and cleanup
//! builder.commit("my-image:latest");
//! builder.remove();
//! ```
//!
//! ## Fluent API Methods Reference
//!
//! | Method | Alias | Description |
//! |--------|-------|-------------|
//! | `.run(command)` | `.sh(command)` | Run a shell command in the container |
//! | `.run_with_isolation(cmd, isolation)` | | Run with specific isolation mode |
//! | `.copy(source, dest)` | | Copy files from host to container |
//! | `.add(source, dest)` | | Add files to container (handles URLs/archives) |
//! | `.config(options)` | | Set config options as a Map |
//! | `.set_entrypoint(ep)` | `.entrypoint(ep)` | Set the container entrypoint |
//! | `.set_cmd(cmd)` | `.cmd(cmd)` | Set the default command |
//! | `.write_content(content, path)` | `.write(content, path)` | Write content to a file |
//! | `.commit(image_name)` | | Commit container to an image |
//!
//! ## Properties Reference
//!
//! | Property | Description |
//! |----------|-------------|
//! | `.stdout` | Standard output from the last command |
//! | `.stderr` | Standard error from the last command |
//! | `.success` | Whether the last command succeeded |
//! | `.code` | Exit code from the last command |
//! | `.result` | Full CommandResult object |
//! | `.container_id` | Container ID |
//! | `.name` | Container name |
//! | `.image` | Base image name |
//! | `.debug` | Get/set debug mode |
//!
//! ## Terminal Methods Reference
//!
//! | Method | Alias | Description |
//! |--------|-------|-------------|
//! | `.read_content(path)` | `.read(path)` | Read content from a file in the container |
//! | `.remove()` | | Remove the container |
//! | `.reset()` | | Remove container and clear state |
//! | `.images()` | | List all images |
//! | `.image_remove(ref)` | | Remove an image |
//! | `.image_pull(name, tls)` | | Pull an image from registry |
//! | `.image_push(ref, dest, tls)` | | Push an image to registry |
//! | `.image_tag(ref, name)` | | Tag an image |
//! | `.build(tag, ctx, file, iso)` | | Build from Dockerfile |

mod builder;
mod cmd;
mod containers;
#[cfg(test)]
mod containers_test;
mod content;
mod images;
mod remote;
pub mod rhai;

use std::error::Error;
use std::fmt;
use std::io;

/// Error type for buildah operations
#[derive(Debug)]
pub enum BuildahError {
    /// The buildah command failed to execute
    CommandExecutionFailed(io::Error),
    /// The buildah command executed but returned an error
    CommandFailed(String),
    /// Failed to parse JSON output
    JsonParseError(String),
    /// Failed to convert data
    ConversionError(String),
    /// Generic error
    Other(String),
}

impl fmt::Display for BuildahError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            BuildahError::CommandExecutionFailed(e) => {
                write!(f, "Failed to execute buildah command: {}", e)
            }
            BuildahError::CommandFailed(e) => write!(f, "Buildah command failed: {}", e),
            BuildahError::JsonParseError(e) => write!(f, "Failed to parse JSON: {}", e),
            BuildahError::ConversionError(e) => write!(f, "Conversion error: {}", e),
            BuildahError::Other(e) => write!(f, "{}", e),
        }
    }
}

impl Error for BuildahError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            BuildahError::CommandExecutionFailed(e) => Some(e),
            _ => None,
        }
    }
}
// Re-export the Builder and BuildahContainer
pub use builder::{BuildahContainer, Builder};

// Re-export existing functions for backward compatibility
pub use cmd::*;
#[deprecated(since = "0.2.0", note = "Use Builder::new() instead")]
pub use containers::*;
pub use content::ContentOperations;
#[deprecated(since = "0.2.0", note = "Use Builder methods instead")]
pub use images::*;

// Re-export remote executors and trait
pub use remote::{BoxedExecutor, KubectlExecutor, LocalExecutor, RemoteExecutor, default_executor};