sal/src/virt/buildah/README.md
timurgordon 65e404e517
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
merge branches and document
2025-05-23 21:12:17 +03:00

15 KiB

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<Builder, BuildahError>
    • 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<CommandResult, BuildahError>: Executes a shell command inside the working container (e.g., buildah run <container> -- <command>).
  • builder.run_with_isolation(command: &str, isolation: &str) -> Result<CommandResult, BuildahError>: Runs a command with specified isolation (e.g., "chroot").
  • builder.copy(source_on_host: &str, dest_in_container: &str) -> Result<CommandResult, BuildahError>: Copies files/directories from the host to the container (buildah copy).
  • builder.add(source_on_host: &str, dest_in_container: &str) -> Result<CommandResult, BuildahError>: Adds files/directories to the container (buildah add), potentially handling URLs and archive extraction.
  • builder.config(options: HashMap<String, String>) -> Result<CommandResult, BuildahError>: Modifies image metadata (e.g., environment variables, labels, entrypoint, cmd). Example options: {"env": "MYVAR=value", "label": "mylabel=myvalue"}.
  • builder.set_entrypoint(entrypoint: &str) -> Result<CommandResult, BuildahError>: Sets the image entrypoint.
  • builder.set_cmd(cmd: &str) -> Result<CommandResult, BuildahError>: Sets the default command for the image.
  • builder.commit(image_name: &str) -> Result<CommandResult, BuildahError>: Commits the current state of the working container to a new image named image_name.
  • builder.remove() -> Result<CommandResult, BuildahError>: 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<Vec<Image>, BuildahError>: Lists all images available locally (buildah images --json). Returns a vector of Image structs.
  • Builder::image_remove(image_ref: &str) -> Result<CommandResult, BuildahError>: Removes an image (buildah rmi <image_ref>).
  • Builder::image_pull(image_name: &str, tls_verify: bool) -> Result<CommandResult, BuildahError>: Pulls an image from a registry (buildah pull).
  • Builder::image_push(image_ref: &str, destination: &str, tls_verify: bool) -> Result<CommandResult, BuildahError>: Pushes an image to a registry (buildah push).
  • Builder::image_tag(image_ref: &str, new_name: &str) -> Result<CommandResult, BuildahError>: Tags an image (buildah tag).
  • Builder::image_commit(container_ref: &str, image_name: &str, format: Option<&str>, squash: bool, rm: bool) -> Result<CommandResult, BuildahError>: 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<CommandResult, BuildahError>: 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.

pub struct Image {
    pub id: String,      // Image ID
    pub names: Vec<String>, // 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<CommandResult, BuildahError>: Writes string content to a file inside the specified container.
  • ContentOperations::read_content(container_id: &str, source_path_in_container: &str) -> Result<String, BuildahError>: 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<CommandResult, BuildahError> (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)

use sal::virt::buildah::{Builder, BuildahError, ContentOperations};
use std::collections::HashMap;

fn build_custom_image() -> Result<String, BuildahError> {
    // 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)

// 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.