# SAL Buildah Module (`sal::virt::buildah`) ## Overview The Buildah module in SAL provides a comprehensive Rust interface for interacting with the `buildah` command-line tool. It allows users to build OCI (Open Container Initiative) and Docker-compatible container images programmatically. The module offers both a high-level `Builder` API for step-by-step image construction and static functions for managing images in local storage. A Rhai script interface for this module is also available via `sal::rhai::buildah`, making these functionalities accessible from `herodo` scripts. ## Core Components ### 1. `Builder` Struct (`sal::virt::buildah::Builder`) The `Builder` struct is the primary entry point for constructing container images. It encapsulates a Buildah working container, created from a base image, and provides methods to modify this container and eventually commit it as a new image. - **Creation**: `Builder::new(name: &str, image: &str) -> Result` - Creates a new working container (or re-attaches to an existing one with the same name) from the specified base `image`. - **Debug Mode**: `builder.set_debug(true)` / `builder.debug()` - Enables/disables verbose logging for Buildah commands executed by this builder instance. #### Working Container Operations: - `builder.run(command: &str) -> Result`: Executes a shell command inside the working container (e.g., `buildah run -- `). - `builder.run_with_isolation(command: &str, isolation: &str) -> Result`: Runs a command with specified isolation (e.g., "chroot"). - `builder.copy(source_on_host: &str, dest_in_container: &str) -> Result`: Copies files/directories from the host to the container (`buildah copy`). - `builder.add(source_on_host: &str, dest_in_container: &str) -> Result`: Adds files/directories to the container (`buildah add`), potentially handling URLs and archive extraction. - `builder.config(options: HashMap) -> Result`: Modifies image metadata (e.g., environment variables, labels, entrypoint, cmd). Example options: `{"env": "MYVAR=value", "label": "mylabel=myvalue"}`. - `builder.set_entrypoint(entrypoint: &str) -> Result`: Sets the image entrypoint. - `builder.set_cmd(cmd: &str) -> Result`: Sets the default command for the image. - `builder.commit(image_name: &str) -> Result`: Commits the current state of the working container to a new image named `image_name`. - `builder.remove() -> Result`: Removes the working container (`buildah rm`). - `builder.reset() -> Result<(), BuildahError>`: Removes the working container and resets the builder state. ### 2. Static Image Management Functions (on `Builder`) These functions operate on images in the local Buildah storage and are not tied to a specific `Builder` instance. - `Builder::images() -> Result, BuildahError>`: Lists all images available locally (`buildah images --json`). Returns a vector of `Image` structs. - `Builder::image_remove(image_ref: &str) -> Result`: Removes an image (`buildah rmi `). - `Builder::image_pull(image_name: &str, tls_verify: bool) -> Result`: Pulls an image from a registry (`buildah pull`). - `Builder::image_push(image_ref: &str, destination: &str, tls_verify: bool) -> Result`: Pushes an image to a registry (`buildah push`). - `Builder::image_tag(image_ref: &str, new_name: &str) -> Result`: Tags an image (`buildah tag`). - `Builder::image_commit(container_ref: &str, image_name: &str, format: Option<&str>, squash: bool, rm: bool) -> Result`: A static version to commit any existing container to an image, with options for format (e.g., "oci", "docker"), squashing layers, and removing the container post-commit. - `Builder::build(tag: Option<&str>, context_dir: &str, file: &str, isolation: Option<&str>) -> Result`: Builds an image from a Dockerfile/Containerfile (`buildah bud`). *Note: Many static image functions also have a `_with_debug(..., debug: bool)` variant for explicit debug control.* ### 3. `Image` Struct (`sal::virt::buildah::Image`) Represents a container image as listed by `buildah images`. ```rust pub struct Image { pub id: String, // Image ID pub names: Vec, // Image names/tags pub size: String, // Image size pub created: String, // Creation timestamp (as string) } ``` ### 4. `ContentOperations` (`sal::virt::buildah::ContentOperations`) Provides static methods for reading and writing file content directly within a container, useful for dynamic configuration or inspection. - `ContentOperations::write_content(container_id: &str, content: &str, dest_path_in_container: &str) -> Result`: Writes string content to a file inside the specified container. - `ContentOperations::read_content(container_id: &str, source_path_in_container: &str) -> Result`: Reads the content of a file from within the specified container into a string. ### 5. `BuildahError` Enum (`sal::virt::buildah::BuildahError`) Defines the error types that can occur during Buildah operations: - `CommandExecutionFailed(io::Error)`: The `buildah` command itself failed to start. - `CommandFailed(String)`: The `buildah` command ran but returned a non-zero exit code or error. - `JsonParseError(String)`: Failed to parse JSON output from Buildah. - `ConversionError(String)`: Error during data conversion. - `Other(String)`: Generic error. ## Key Design Points The SAL Buildah module is designed with the following principles: - **Builder Pattern**: The `Builder` struct (`sal::virt::buildah::Builder`) employs a builder pattern, enabling a fluent, step-by-step, and stateful approach to constructing container images. Each `Builder` instance manages a specific working container. - **Separation of Concerns**: - **Instance Methods**: Operations specific to a working container (e.g., `run`, `copy`, `config`, `commit`) are methods on the `Builder` instance. - **Static Methods**: General image management tasks (e.g., listing images with `Builder::images()`, removing images with `Builder::image_remove()`, pulling, pushing, tagging, and building from a Dockerfile with `Builder::build()`) are provided as static functions on the `Builder` struct. - **Direct Content Manipulation**: The `ContentOperations` struct provides static methods (`write_content`, `read_content`) to directly interact with files within a Buildah container. This is typically achieved by temporarily mounting the container or using `buildah add` with temporary files, abstracting the complexity from the user. - **Debuggability**: Fine-grained control over `buildah` command logging is provided. The `builder.set_debug(true)` method enables verbose output for a specific `Builder` instance. Many static functions also offer `_with_debug(..., debug: bool)` variants. This is managed internally via a thread-local flag passed to the core `execute_buildah_command` function. - **Comprehensive Rhai Integration**: Most functionalities of the Buildah module are exposed to Rhai scripts executed via `herodo`, allowing for powerful automation of image building workflows. This is facilitated by the `sal::rhai::buildah` module. ## Low-Level Command Execution - `execute_buildah_command(args: &[&str]) -> Result` (in `sal::virt::buildah::cmd`): The core function that executes `buildah` commands. It handles debug logging based on a thread-local flag, which is managed by the higher-level `Builder` methods and `_with_debug` static function variants. ## Usage Example (Rust) ```rust use sal::virt::buildah::{Builder, BuildahError, ContentOperations}; use std::collections::HashMap; fn build_custom_image() -> Result { // Create a new builder from a base image (e.g., alpine) let mut builder = Builder::new("my-custom-container", "docker.io/library/alpine:latest")?; builder.set_debug(true); // Run some commands builder.run("apk add --no-cache curl")?; builder.run("mkdir /app")?; // Add a file ContentOperations::write_content(builder.container_id().unwrap(), "Hello from SAL!", "/app/hello.txt")?; // Set image configuration let mut config_opts = HashMap::new(); config_opts.insert("workingdir".to_string(), "/app".to_string()); config_opts.insert("label".to_string(), "maintainer=sal-user".to_string()); builder.config(config_opts)?; builder.set_entrypoint("["/usr/bin/curl"]")?; builder.set_cmd("["http://ifconfig.me"]")?; // Commit the image let image_tag = "localhost/my-custom-image:latest"; builder.commit(image_tag)?; println!("Successfully built image: {}", image_tag); // Clean up the working container builder.remove()?; Ok(image_tag.to_string()) } fn main() { match build_custom_image() { Ok(tag) => println!("Image {} created.", tag), Err(e) => eprintln!("Error building image: {}", e), } } ``` ## Rhai Scripting with `herodo` The Buildah module's capabilities are extensively exposed to Rhai scripts, enabling automation of image building and management tasks via the `herodo` CLI tool. The `sal::rhai::buildah` module registers the necessary functions and types. Below is a summary of commonly used Rhai functions for Buildah. (Note: `builder` refers to an instance of `BuildahBuilder` obtained typically via `bah_new`). ### Builder Object Management - `bah_new(name: String, image: String) -> BuildahBuilder`: Creates a new Buildah builder instance (working container) from a base `image` with a given `name`. - `builder.remove()`: Removes the working container associated with the `builder`. - `builder.reset()`: Removes the working container and resets the `builder` state. ### Builder Configuration & Operations - `builder.set_debug(is_debug: bool)`: Enables or disables verbose debug logging for commands executed by this `builder`. - `builder.debug_mode` (property): Get or set the debug mode (e.g., `let mode = builder.debug_mode; builder.debug_mode = true;`). - `builder.container_id` (property): Returns the ID of the working container (e.g., `let id = builder.container_id;`). - `builder.name` (property): Returns the name of the builder/working container. - `builder.image` (property): Returns the base image name used by the builder. - `builder.run(command: String)`: Executes a shell command inside the `builder`'s working container. - `builder.run_with_isolation(command: String, isolation: String)`: Runs a command with specified isolation (e.g., "chroot"). - `builder.copy(source_on_host: String, dest_in_container: String)`: Copies files/directories from the host to the `builder`'s container. - `builder.add(source_on_host: String, dest_in_container: String)`: Adds files/directories to the `builder`'s container (can handle URLs and auto-extract archives). - `builder.config(options: Map)`: Modifies image metadata. `options` is a Rhai map, e.g., `#{ "env": "MYVAR=value", "label": "foo=bar" }`. - `builder.set_entrypoint(entrypoint: String)`: Sets the image entrypoint (e.g., `builder.set_entrypoint("[/app/run.sh]")`). - `builder.set_cmd(cmd: String)`: Sets the default command for the image (e.g., `builder.set_cmd("[--help]")`). - `builder.commit(image_tag: String)`: Commits the current state of the `builder`'s working container to a new image with `image_tag`. ### Content Operations (with a Builder instance) - `bah_write_content(builder: BuildahBuilder, content: String, dest_path_in_container: String)`: Writes string `content` to a file at `dest_path_in_container` inside the `builder`'s container. - `bah_read_content(builder: BuildahBuilder, source_path_in_container: String) -> String`: Reads the content of a file from `source_path_in_container` within the `builder`'s container. ### Global Image Operations These functions generally correspond to static methods in Rust and operate on the local Buildah image storage. - `bah_images() -> Array`: Lists all images available locally. Returns an array of `BuildahImage` objects. - `bah_image_remove(image_ref: String)`: Removes an image (e.g., by ID or tag) from local storage. - `bah_image_pull(image_name: String, tls_verify: bool)`: Pulls an image from a registry. - `bah_image_push(image_ref: String, destination: String, tls_verify: bool)`: Pushes a local image to a registry. - `bah_image_tag(image_ref: String, new_name: String)`: Adds a new tag (`new_name`) to an existing image (`image_ref`). - `bah_build(tag: String, context_dir: String, file: String, isolation: String)`: Builds an image from a Dockerfile/Containerfile (equivalent to `buildah bud`). `file` is the path to the Dockerfile relative to `context_dir`. `isolation` can be e.g., "chroot". ### Example `herodo` Rhai Script (Revisited) ```rhai // Create a new builder let builder = bah_new("my-rhai-app", "docker.io/library/alpine:latest"); builder.debug_mode = true; // Enable debug logging for this builder // Run commands in the container builder.run("apk add --no-cache figlet curl"); builder.run("mkdir /data"); // Write content to a file in the container bah_write_content(builder, "Hello from SAL Buildah via Rhai!", "/data/message.txt"); // Configure image metadata builder.config(#{ "env": "APP_VERSION=1.0", "label": "author=HerodoUser" }); builder.set_entrypoint('["figlet"]'); builder.set_cmd('["Rhai Build"]'); // Commit the image let image_name = "localhost/my-rhai-app:v1"; builder.commit(image_name); print(`Image committed: ${image_name}`); // Clean up the working container builder.remove(); print("Builder container removed."); // List local images print("Current local images:"); let images = bah_images(); for img in images { print(` ID: ${img.id}, Name(s): ${img.names}, Size: ${img.size}`); } // Example: Build from a Dockerfile (assuming Dockerfile exists at /tmp/build_context/Dockerfile) // Ensure /tmp/build_context/Dockerfile exists with simple content like: // FROM alpine // RUN echo "Built with bah_build" > /built.txt // CMD cat /built.txt // // if exist("/tmp/build_context/Dockerfile") { // print("Building from Dockerfile..."); // bah_build("localhost/from-dockerfile:latest", "/tmp/build_context", "Dockerfile", "chroot"); // print("Dockerfile build complete."); // bah_image_remove("localhost/from-dockerfile:latest"); // Clean up // } else { // print("Skipping Dockerfile build example: /tmp/build_context/Dockerfile not found."); // } ``` This README provides a guide to using the SAL Buildah module. For more detailed information on specific functions and their parameters, consult the Rust doc comments within the source code.