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
.
- Creates a new working container (or re-attaches to an existing one with the same name) from the specified base
- 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 namedimage_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 ofImage
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)
: Thebuildah
command itself failed to start.CommandFailed(String)
: Thebuildah
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. EachBuilder
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 theBuilder
instance. - Static Methods: General image management tasks (e.g., listing images with
Builder::images()
, removing images withBuilder::image_remove()
, pulling, pushing, tagging, and building from a Dockerfile withBuilder::build()
) are provided as static functions on theBuilder
struct.
- Instance Methods: Operations specific to a working container (e.g.,
- 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 usingbuildah add
with temporary files, abstracting the complexity from the user. - Debuggability: Fine-grained control over
buildah
command logging is provided. Thebuilder.set_debug(true)
method enables verbose output for a specificBuilder
instance. Many static functions also offer_with_debug(..., debug: bool)
variants. This is managed internally via a thread-local flag passed to the coreexecute_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 thesal::rhai::buildah
module.
Low-Level Command Execution
execute_buildah_command(args: &[&str]) -> Result<CommandResult, BuildahError>
(insal::virt::buildah::cmd
): The core function that executesbuildah
commands. It handles debug logging based on a thread-local flag, which is managed by the higher-levelBuilder
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 baseimage
with a givenname
.builder.remove()
: Removes the working container associated with thebuilder
.builder.reset()
: Removes the working container and resets thebuilder
state.
Builder Configuration & Operations
builder.set_debug(is_debug: bool)
: Enables or disables verbose debug logging for commands executed by thisbuilder
.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 thebuilder
'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 thebuilder
's container.builder.add(source_on_host: String, dest_in_container: String)
: Adds files/directories to thebuilder
'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 thebuilder
's working container to a new image withimage_tag
.
Content Operations (with a Builder instance)
bah_write_content(builder: BuildahBuilder, content: String, dest_path_in_container: String)
: Writes stringcontent
to a file atdest_path_in_container
inside thebuilder
's container.bah_read_content(builder: BuildahBuilder, source_path_in_container: String) -> String
: Reads the content of a file fromsource_path_in_container
within thebuilder
'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 ofBuildahImage
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 tobuildah bud
).file
is the path to the Dockerfile relative tocontext_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.