# 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.
## Fluent API (Recommended for Rhai)
The `bah()` function provides a clean, chainable API for building container images in Rhai scripts:
```rhai
// Build a web server image
let b = bah("webserver", "alpine:latest")
.run("apk update")
.run("apk add --no-cache nginx")
.write(`
server {
listen 80;
root /var/www/html;
}
`, "/etc/nginx/conf.d/default.conf")
.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.trim()}`);
}
// Clean up
b.remove();
```
### Chainable Methods
| `.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 into the container |
| `.add(source, dest)` | | Add files into the container |
| `.config(options)` | | Set config options (as Map) |
| `.set_entrypoint(entrypoint)` | `.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
| `.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
| `.read_content(path)` | `.read(path)` | Read content from a file in the container |
| `.remove()` | | Remove the container |
| `.reset()` | | Remove container and clear state |
## 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`.
```rust
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)
```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.
### Fluent API (Recommended)
The `bah()` function is the recommended way to build container images in Rhai. See the [Fluent API section](#fluent-api-recommended-for-rhai) at the top of this document for full documentation.
```rhai
// Build a complete container image with method chaining
let b = bah("webserver", "alpine:latest")
.run("apk update")
.run("apk add --no-cache nginx")
.write("<h1>Hello</h1>", "/var/www/html/index.html")
.entrypoint("/usr/sbin/nginx")
.cmd("-g 'daemon off;'")
.commit("my-webserver:latest");
if b.success {
print(`Image built: ${b.stdout.trim()}`);
}
b.remove();
```
### Legacy API
The legacy `bah_new()` function is still available for backward compatibility:
- `bah_new(name: String, image: String) -> BuildahBuilder`: Creates a new Buildah builder instance.
- `builder.remove()`: Removes the working container.
- `builder.reset()`: Removes the working container and resets the `builder` state.
- `builder.debug_mode` (property): Get or set the debug mode.
- `builder.container_id` (property): Returns the container ID.
- `builder.name` (property): Returns the builder name.
- `builder.image` (property): Returns the base image name.
- `builder.run(command: String)`: Executes a shell command.
- `builder.run_with_isolation(command: String, isolation: String)`: Runs a command with specified isolation.
- `builder.copy(source, dest)`: Copies files into the container.
- `builder.add(source, dest)`: Adds files into the container.
- `builder.config(options: Map)`: Modifies image metadata.
- `builder.set_entrypoint(entrypoint: String)`: Sets the image entrypoint.
- `builder.set_cmd(cmd: String)`: Sets the default command.
- `builder.commit(image_tag: String)`: Commits the container to an image.
- `builder.write_content(content, path)`: Writes content to a file in the container.
- `builder.read_content(path)`: Reads content from a file in the container.
- `builder.images()`: Lists images in local storage.
- `builder.image_remove(image)`: Removes an image.
- `builder.image_pull(image, tls_verify)`: Pulls an image from a registry.
- `builder.image_push(image, destination, tls_verify)`: Pushes an image to a registry.
- `builder.image_tag(image, new_name)`: Adds a tag to an image.
- `builder.build(tag, context_dir, file, isolation)`: Builds an image from a Dockerfile.
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.