...
This commit is contained in:
parent
9f33c94020
commit
e48063a79c
752
buildah_builder_implementation_plan.md
Normal file
752
buildah_builder_implementation_plan.md
Normal file
@ -0,0 +1,752 @@
|
|||||||
|
# Buildah Builder Implementation Plan
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This document outlines the plan for changing the buildah interface in the `@src/virt/buildah` module to use a builder object pattern. The current implementation uses standalone functions, which makes the interface less clear and harder to use. The new implementation will use a builder object with methods, which will make the interface more intuitive and easier to use.
|
||||||
|
|
||||||
|
## Current Architecture
|
||||||
|
|
||||||
|
The current buildah implementation has:
|
||||||
|
- Standalone functions in the buildah module (from, run, images, etc.)
|
||||||
|
- Functions that operate on container IDs passed as parameters
|
||||||
|
- No state maintained between function calls
|
||||||
|
- Rhai wrappers that expose these functions to Rhai scripts
|
||||||
|
|
||||||
|
Example of current usage:
|
||||||
|
```rust
|
||||||
|
// Create a container
|
||||||
|
let result = buildah::from("fedora:latest")?;
|
||||||
|
let container_id = result.stdout.trim();
|
||||||
|
|
||||||
|
// Run a command in the container
|
||||||
|
buildah::run(container_id, "dnf install -y nginx")?;
|
||||||
|
|
||||||
|
// Copy a file into the container
|
||||||
|
buildah::bah_copy(container_id, "./example.conf", "/etc/example.conf")?;
|
||||||
|
|
||||||
|
// Commit the container to create a new image
|
||||||
|
buildah::bah_commit(container_id, "my-nginx:latest")?;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Proposed Architecture
|
||||||
|
|
||||||
|
We'll change to a builder object pattern where:
|
||||||
|
- A `Builder` struct is created with a `new()` method that takes a name and image
|
||||||
|
- All operations (including those not specific to a container) are methods on the Builder
|
||||||
|
- The Builder maintains state (like container ID) between method calls
|
||||||
|
- Methods return operation results (CommandResult or other types) for error handling
|
||||||
|
- Rhai wrappers expose the Builder and its methods to Rhai scripts
|
||||||
|
|
||||||
|
Example of proposed usage:
|
||||||
|
```rust
|
||||||
|
// Create a builder
|
||||||
|
let builder = Builder::new("my-container", "fedora:latest")?;
|
||||||
|
|
||||||
|
// Run a command in the container
|
||||||
|
builder.run("dnf install -y nginx")?;
|
||||||
|
|
||||||
|
// Copy a file into the container
|
||||||
|
builder.copy("./example.conf", "/etc/example.conf")?;
|
||||||
|
|
||||||
|
// Commit the container to create a new image
|
||||||
|
builder.commit("my-nginx:latest")?;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Class Diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Builder {
|
||||||
|
-String name
|
||||||
|
-String container_id
|
||||||
|
-String image
|
||||||
|
+new(name: String, image: String) -> Result<Builder, BuildahError>
|
||||||
|
+run(command: String) -> Result<CommandResult, BuildahError>
|
||||||
|
+run_with_isolation(command: String, isolation: String) -> Result<CommandResult, BuildahError>
|
||||||
|
+add(source: String, dest: String) -> Result<CommandResult, BuildahError>
|
||||||
|
+copy(source: String, dest: String) -> Result<CommandResult, BuildahError>
|
||||||
|
+commit(image_name: String) -> Result<CommandResult, BuildahError>
|
||||||
|
+remove() -> Result<CommandResult, BuildahError>
|
||||||
|
+config(options: HashMap<String, String>) -> Result<CommandResult, BuildahError>
|
||||||
|
}
|
||||||
|
|
||||||
|
class BuilderStatic {
|
||||||
|
+images() -> Result<Vec<Image>, BuildahError>
|
||||||
|
+image_remove(image: String) -> Result<CommandResult, BuildahError>
|
||||||
|
+image_pull(image: String, tls_verify: bool) -> Result<CommandResult, BuildahError>
|
||||||
|
+image_push(image: String, destination: String, tls_verify: bool) -> Result<CommandResult, BuildahError>
|
||||||
|
+image_tag(image: String, new_name: String) -> Result<CommandResult, BuildahError>
|
||||||
|
+image_commit(container: String, image_name: String, format: Option<String>, squash: bool, rm: bool) -> Result<CommandResult, BuildahError>
|
||||||
|
+build(tag: Option<String>, context_dir: String, file: String, isolation: Option<String>) -> Result<CommandResult, BuildahError>
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder --|> BuilderStatic : Static methods
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### Step 1: Create the Builder Struct
|
||||||
|
|
||||||
|
1. Create a new file `src/virt/buildah/builder.rs`
|
||||||
|
2. Define the Builder struct with fields for name, container_id, and image
|
||||||
|
3. Implement the new() method that creates a container from the image and returns a Builder instance
|
||||||
|
4. Implement methods for all container operations (run, add, copy, commit, etc.)
|
||||||
|
5. Implement methods for all image operations (images, image_remove, image_pull, etc.)
|
||||||
|
|
||||||
|
### Step 2: Update the Module Structure
|
||||||
|
|
||||||
|
1. Update `src/virt/buildah/mod.rs` to include the new builder module
|
||||||
|
2. Re-export the Builder struct and its methods
|
||||||
|
3. Keep the existing functions for backward compatibility (marked as deprecated)
|
||||||
|
|
||||||
|
### Step 3: Update the Rhai Wrapper
|
||||||
|
|
||||||
|
1. Update `src/rhai/buildah.rs` to register the Builder type with the Rhai engine
|
||||||
|
2. Register all Builder methods with the Rhai engine
|
||||||
|
3. Create Rhai-friendly constructor for the Builder
|
||||||
|
4. Update the existing function wrappers to use the new Builder (or keep them for backward compatibility)
|
||||||
|
|
||||||
|
### Step 4: Update Examples and Tests
|
||||||
|
|
||||||
|
1. Update `examples/buildah.rs` to use the new Builder pattern
|
||||||
|
2. Update `rhaiexamples/04_buildah_operations.rhai` to use the new Builder pattern
|
||||||
|
3. Update any tests to use the new Builder pattern
|
||||||
|
|
||||||
|
## Detailed Implementation
|
||||||
|
|
||||||
|
### 1. Builder Struct Definition
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// src/virt/buildah/builder.rs
|
||||||
|
pub struct Builder {
|
||||||
|
name: String,
|
||||||
|
container_id: Option<String>,
|
||||||
|
image: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
pub fn new(name: &str, image: &str) -> Result<Self, BuildahError> {
|
||||||
|
let result = execute_buildah_command(&["from", "--name", name, image])?;
|
||||||
|
let container_id = result.stdout.trim().to_string();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
container_id: Some(container_id),
|
||||||
|
image: image.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container methods
|
||||||
|
pub fn run(&self, command: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["run", container_id, "sh", "-c", command])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_with_isolation(&self, command: &str, isolation: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["run", "--isolation", isolation, container_id, "sh", "-c", command])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy(&self, source: &str, dest: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["copy", container_id, source, dest])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&self, source: &str, dest: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["add", container_id, source, dest])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit(&self, image_name: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["commit", container_id, image_name])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&self) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["rm", container_id])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config(&self, options: HashMap<String, String>) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
let mut args_owned: Vec<String> = Vec::new();
|
||||||
|
args_owned.push("config".to_string());
|
||||||
|
|
||||||
|
// Process options map
|
||||||
|
for (key, value) in options.iter() {
|
||||||
|
let option_name = format!("--{}", key);
|
||||||
|
args_owned.push(option_name);
|
||||||
|
args_owned.push(value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
args_owned.push(container_id.clone());
|
||||||
|
|
||||||
|
// Convert Vec<String> to Vec<&str> for execute_buildah_command
|
||||||
|
let args: Vec<&str> = args_owned.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
|
execute_buildah_command(&args)
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static methods
|
||||||
|
pub fn images() -> Result<Vec<Image>, BuildahError> {
|
||||||
|
// Implementation from current images() function
|
||||||
|
let result = execute_buildah_command(&["images", "--json"])?;
|
||||||
|
|
||||||
|
// Try to parse the JSON output
|
||||||
|
match serde_json::from_str::<serde_json::Value>(&result.stdout) {
|
||||||
|
Ok(json) => {
|
||||||
|
if let serde_json::Value::Array(images_json) = json {
|
||||||
|
let mut images = Vec::new();
|
||||||
|
|
||||||
|
for image_json in images_json {
|
||||||
|
// Extract image ID
|
||||||
|
let id = match image_json.get("id").and_then(|v| v.as_str()) {
|
||||||
|
Some(id) => id.to_string(),
|
||||||
|
None => return Err(BuildahError::ConversionError("Missing image ID".to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract image names
|
||||||
|
let names = match image_json.get("names").and_then(|v| v.as_array()) {
|
||||||
|
Some(names_array) => {
|
||||||
|
let mut names_vec = Vec::new();
|
||||||
|
for name_value in names_array {
|
||||||
|
if let Some(name_str) = name_value.as_str() {
|
||||||
|
names_vec.push(name_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names_vec
|
||||||
|
},
|
||||||
|
None => Vec::new(), // Empty vector if no names found
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract image size
|
||||||
|
let size = match image_json.get("size").and_then(|v| v.as_str()) {
|
||||||
|
Some(size) => size.to_string(),
|
||||||
|
None => "Unknown".to_string(), // Default value if size not found
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract creation timestamp
|
||||||
|
let created = match image_json.get("created").and_then(|v| v.as_str()) {
|
||||||
|
Some(created) => created.to_string(),
|
||||||
|
None => "Unknown".to_string(), // Default value if created not found
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create Image struct and add to vector
|
||||||
|
images.push(Image {
|
||||||
|
id,
|
||||||
|
names,
|
||||||
|
size,
|
||||||
|
created,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(images)
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::JsonParseError("Expected JSON array".to_string()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
Err(BuildahError::JsonParseError(format!("Failed to parse image list JSON: {}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_remove(image: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
execute_buildah_command(&["rmi", image])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_pull(image: &str, tls_verify: bool) -> Result<CommandResult, BuildahError> {
|
||||||
|
let mut args = vec!["pull"];
|
||||||
|
|
||||||
|
if !tls_verify {
|
||||||
|
args.push("--tls-verify=false");
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(image);
|
||||||
|
|
||||||
|
execute_buildah_command(&args)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_push(image: &str, destination: &str, tls_verify: bool) -> Result<CommandResult, BuildahError> {
|
||||||
|
let mut args = vec!["push"];
|
||||||
|
|
||||||
|
if !tls_verify {
|
||||||
|
args.push("--tls-verify=false");
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(image);
|
||||||
|
args.push(destination);
|
||||||
|
|
||||||
|
execute_buildah_command(&args)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_tag(image: &str, new_name: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
execute_buildah_command(&["tag", image, new_name])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_commit(container: &str, image_name: &str, format: Option<&str>, squash: bool, rm: bool) -> Result<CommandResult, BuildahError> {
|
||||||
|
let mut args = vec!["commit"];
|
||||||
|
|
||||||
|
if let Some(format_str) = format {
|
||||||
|
args.push("--format");
|
||||||
|
args.push(format_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
if squash {
|
||||||
|
args.push("--squash");
|
||||||
|
}
|
||||||
|
|
||||||
|
if rm {
|
||||||
|
args.push("--rm");
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(container);
|
||||||
|
args.push(image_name);
|
||||||
|
|
||||||
|
execute_buildah_command(&args)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(tag: Option<&str>, context_dir: &str, file: &str, isolation: Option<&str>) -> Result<CommandResult, BuildahError> {
|
||||||
|
let mut args = Vec::new();
|
||||||
|
args.push("build");
|
||||||
|
|
||||||
|
if let Some(tag_value) = tag {
|
||||||
|
args.push("-t");
|
||||||
|
args.push(tag_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(isolation_value) = isolation {
|
||||||
|
args.push("--isolation");
|
||||||
|
args.push(isolation_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push("-f");
|
||||||
|
args.push(file);
|
||||||
|
|
||||||
|
args.push(context_dir);
|
||||||
|
|
||||||
|
execute_buildah_command(&args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Updated Module Structure
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// src/virt/buildah/mod.rs
|
||||||
|
mod containers;
|
||||||
|
mod images;
|
||||||
|
mod cmd;
|
||||||
|
mod builder;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod containers_test;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::error::Error;
|
||||||
|
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
|
||||||
|
pub use builder::Builder;
|
||||||
|
|
||||||
|
// Re-export existing functions for backward compatibility
|
||||||
|
#[deprecated(since = "0.2.0", note = "Use Builder::new() instead")]
|
||||||
|
pub use containers::*;
|
||||||
|
#[deprecated(since = "0.2.0", note = "Use Builder methods instead")]
|
||||||
|
pub use images::*;
|
||||||
|
pub use cmd::*;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Rhai Wrapper Changes
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// src/rhai/buildah.rs
|
||||||
|
//! Rhai wrappers for Buildah module functions
|
||||||
|
//!
|
||||||
|
//! This module provides Rhai wrappers for the functions in the Buildah module.
|
||||||
|
|
||||||
|
use rhai::{Engine, EvalAltResult, Array, Dynamic, Map};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::virt::buildah::{self, BuildahError, Image, Builder};
|
||||||
|
use crate::process::CommandResult;
|
||||||
|
|
||||||
|
/// Register Buildah module functions with the Rhai engine
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `engine` - The Rhai engine to register the functions with
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
|
||||||
|
pub fn register_bah_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||||
|
// Register types
|
||||||
|
register_bah_types(engine)?;
|
||||||
|
|
||||||
|
// Register Builder constructor
|
||||||
|
engine.register_fn("bah_new", bah_new);
|
||||||
|
|
||||||
|
// Register Builder instance methods
|
||||||
|
engine.register_fn("run", |builder: &mut Builder, command: &str| -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.run(command))
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("run_with_isolation", |builder: &mut Builder, command: &str, isolation: &str| -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.run_with_isolation(command, isolation))
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("copy", |builder: &mut Builder, source: &str, dest: &str| -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.copy(source, dest))
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("add", |builder: &mut Builder, source: &str, dest: &str| -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.add(source, dest))
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("commit", |builder: &mut Builder, image_name: &str| -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.commit(image_name))
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("remove", |builder: &mut Builder| -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.remove())
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("config", |builder: &mut Builder, options: Map| -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
// Convert Rhai Map to Rust HashMap
|
||||||
|
let config_options = convert_map_to_hashmap(options)?;
|
||||||
|
bah_error_to_rhai_error(builder.config(config_options))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register Builder static methods
|
||||||
|
engine.register_fn("images", |_: &mut Builder| -> Result<Array, Box<EvalAltResult>> {
|
||||||
|
let images = bah_error_to_rhai_error(Builder::images())?;
|
||||||
|
|
||||||
|
// Convert Vec<Image> to Rhai Array
|
||||||
|
let mut array = Array::new();
|
||||||
|
for image in images {
|
||||||
|
array.push(Dynamic::from(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(array)
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("image_remove", |_: &mut Builder, image: &str| -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(Builder::image_remove(image))
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("image_pull", |_: &mut Builder, image: &str, tls_verify: bool| -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(Builder::image_pull(image, tls_verify))
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("image_push", |_: &mut Builder, image: &str, destination: &str, tls_verify: bool| -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(Builder::image_push(image, destination, tls_verify))
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_fn("image_tag", |_: &mut Builder, image: &str, new_name: &str| -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(Builder::image_tag(image, new_name))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register legacy functions for backward compatibility
|
||||||
|
register_legacy_functions(engine)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register Buildah module types with the Rhai engine
|
||||||
|
fn register_bah_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||||
|
// Register Builder type
|
||||||
|
engine.register_type_with_name::<Builder>("BuildahBuilder");
|
||||||
|
|
||||||
|
// Register getters for Builder properties
|
||||||
|
engine.register_get("container_id", |builder: &mut Builder| {
|
||||||
|
match builder.container_id() {
|
||||||
|
Some(id) => id.clone(),
|
||||||
|
None => "".to_string(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.register_get("name", |builder: &mut Builder| builder.name().to_string());
|
||||||
|
engine.register_get("image", |builder: &mut Builder| builder.image().to_string());
|
||||||
|
|
||||||
|
// Register Image type and methods (same as before)
|
||||||
|
engine.register_type_with_name::<Image>("BuildahImage");
|
||||||
|
|
||||||
|
// Register getters for Image properties
|
||||||
|
engine.register_get("id", |img: &mut Image| img.id.clone());
|
||||||
|
engine.register_get("names", |img: &mut Image| {
|
||||||
|
let mut array = Array::new();
|
||||||
|
for name in &img.names {
|
||||||
|
array.push(Dynamic::from(name.clone()));
|
||||||
|
}
|
||||||
|
array
|
||||||
|
});
|
||||||
|
// Add a 'name' getter that returns the first name or a default
|
||||||
|
engine.register_get("name", |img: &mut Image| {
|
||||||
|
if img.names.is_empty() {
|
||||||
|
"<none>".to_string()
|
||||||
|
} else {
|
||||||
|
img.names[0].clone()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
engine.register_get("size", |img: &mut Image| img.size.clone());
|
||||||
|
engine.register_get("created", |img: &mut Image| img.created.clone());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register legacy functions for backward compatibility
|
||||||
|
fn register_legacy_functions(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||||
|
// Register container functions
|
||||||
|
engine.register_fn("bah_from", bah_from_legacy);
|
||||||
|
engine.register_fn("bah_run", bah_run_legacy);
|
||||||
|
engine.register_fn("bah_run_with_isolation", bah_run_with_isolation_legacy);
|
||||||
|
engine.register_fn("bah_copy", bah_copy_legacy);
|
||||||
|
engine.register_fn("bah_add", bah_add_legacy);
|
||||||
|
engine.register_fn("bah_commit", bah_commit_legacy);
|
||||||
|
engine.register_fn("bah_remove", bah_remove_legacy);
|
||||||
|
engine.register_fn("bah_list", bah_list_legacy);
|
||||||
|
engine.register_fn("bah_build", bah_build_with_options_legacy);
|
||||||
|
engine.register_fn("bah_new_build_options", new_build_options);
|
||||||
|
|
||||||
|
// Register image functions
|
||||||
|
engine.register_fn("bah_images", images_legacy);
|
||||||
|
engine.register_fn("bah_image_remove", image_remove_legacy);
|
||||||
|
engine.register_fn("bah_image_push", image_push_legacy);
|
||||||
|
engine.register_fn("bah_image_tag", image_tag_legacy);
|
||||||
|
engine.register_fn("bah_image_pull", image_pull_legacy);
|
||||||
|
engine.register_fn("bah_image_commit", image_commit_with_options_legacy);
|
||||||
|
engine.register_fn("bah_new_commit_options", new_commit_options);
|
||||||
|
engine.register_fn("bah_config", config_with_options_legacy);
|
||||||
|
engine.register_fn("bah_new_config_options", new_config_options);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions for error conversion
|
||||||
|
fn bah_error_to_rhai_error<T>(result: Result<T, BuildahError>) -> Result<T, Box<EvalAltResult>> {
|
||||||
|
result.map_err(|e| {
|
||||||
|
Box::new(EvalAltResult::ErrorRuntime(
|
||||||
|
format!("Buildah error: {}", e).into(),
|
||||||
|
rhai::Position::NONE
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to convert Rhai Map to Rust HashMap
|
||||||
|
fn convert_map_to_hashmap(options: Map) -> Result<HashMap<String, String>, Box<EvalAltResult>> {
|
||||||
|
let mut config_options = HashMap::<String, String>::new();
|
||||||
|
|
||||||
|
for (key, value) in options.iter() {
|
||||||
|
if let Ok(value_str) = value.clone().into_string() {
|
||||||
|
// Convert SmartString to String
|
||||||
|
config_options.insert(key.to_string(), value_str);
|
||||||
|
} else {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||||
|
format!("Option '{}' must be a string", key).into(),
|
||||||
|
rhai::Position::NONE
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config_options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new Builder
|
||||||
|
pub fn bah_new(name: &str, image: &str) -> Result<Builder, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(Builder::new(name, image))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy function implementations (for backward compatibility)
|
||||||
|
// These would call the new Builder methods internally
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Example Updates
|
||||||
|
|
||||||
|
#### Rust Example
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// examples/buildah.rs
|
||||||
|
//! Example usage of the buildah module
|
||||||
|
//!
|
||||||
|
//! This file demonstrates how to use the buildah module to perform
|
||||||
|
//! common container operations like creating containers, running commands,
|
||||||
|
//! and managing images.
|
||||||
|
|
||||||
|
use sal::virt::buildah::{Builder, BuildahError};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Run a complete buildah workflow example
|
||||||
|
pub fn run_buildah_example() -> Result<(), BuildahError> {
|
||||||
|
println!("Starting buildah example workflow...");
|
||||||
|
|
||||||
|
// Step 1: Create a container from an image using the Builder
|
||||||
|
println!("\n=== Creating container from fedora:latest ===");
|
||||||
|
let builder = Builder::new("my-fedora-container", "fedora:latest")?;
|
||||||
|
println!("Created container: {}", builder.container_id().unwrap_or(&"unknown".to_string()));
|
||||||
|
|
||||||
|
// Step 2: Run a command in the container
|
||||||
|
println!("\n=== Installing nginx in container ===");
|
||||||
|
// Use chroot isolation to avoid BPF issues
|
||||||
|
let install_result = builder.run_with_isolation("dnf install -y nginx", "chroot")?;
|
||||||
|
println!("{:#?}", install_result);
|
||||||
|
println!("Installation output: {}", install_result.stdout);
|
||||||
|
|
||||||
|
// Step 3: Copy a file into the container
|
||||||
|
println!("\n=== Copying configuration file to container ===");
|
||||||
|
builder.copy("./example.conf", "/etc/example.conf")?;
|
||||||
|
|
||||||
|
// Step 4: Configure container metadata
|
||||||
|
println!("\n=== Configuring container metadata ===");
|
||||||
|
let mut config_options = HashMap::new();
|
||||||
|
config_options.insert("port".to_string(), "80".to_string());
|
||||||
|
config_options.insert("label".to_string(), "maintainer=example@example.com".to_string());
|
||||||
|
config_options.insert("entrypoint".to_string(), "/usr/sbin/nginx".to_string());
|
||||||
|
builder.config(config_options)?;
|
||||||
|
println!("Container configured");
|
||||||
|
|
||||||
|
// Step 5: Commit the container to create a new image
|
||||||
|
println!("\n=== Committing container to create image ===");
|
||||||
|
let image_name = "my-nginx:latest";
|
||||||
|
builder.commit(image_name)?;
|
||||||
|
println!("Created image: {}", image_name);
|
||||||
|
|
||||||
|
// Step 6: List images to verify our new image exists
|
||||||
|
println!("\n=== Listing images ===");
|
||||||
|
let images = Builder::images()?;
|
||||||
|
println!("Found {} images:", images.len());
|
||||||
|
for image in images {
|
||||||
|
println!(" ID: {}", image.id);
|
||||||
|
println!(" Names: {}", image.names.join(", "));
|
||||||
|
println!(" Size: {}", image.size);
|
||||||
|
println!(" Created: {}", image.created);
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 7: Clean up (optional in a real workflow)
|
||||||
|
println!("\n=== Cleaning up ===");
|
||||||
|
Builder::image_remove(image_name)?;
|
||||||
|
builder.remove()?;
|
||||||
|
|
||||||
|
println!("\nBuildah example workflow completed successfully!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Demonstrate how to build an image from a Containerfile/Dockerfile
|
||||||
|
pub fn build_image_example() -> Result<(), BuildahError> {
|
||||||
|
println!("Building an image from a Containerfile...");
|
||||||
|
|
||||||
|
// Use the build function with tag, context directory, and isolation to avoid BPF issues
|
||||||
|
let result = Builder::build(Some("my-app:latest"), ".", "example_Dockerfile", Some("chroot"))?;
|
||||||
|
|
||||||
|
println!("Build output: {}", result.stdout);
|
||||||
|
println!("Image built successfully!");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Example of pulling and pushing images
|
||||||
|
pub fn registry_operations_example() -> Result<(), BuildahError> {
|
||||||
|
println!("Demonstrating registry operations...");
|
||||||
|
|
||||||
|
// Pull an image
|
||||||
|
println!("\n=== Pulling an image ===");
|
||||||
|
Builder::image_pull("docker.io/library/alpine:latest", true)?;
|
||||||
|
println!("Image pulled successfully");
|
||||||
|
|
||||||
|
// Tag the image
|
||||||
|
println!("\n=== Tagging the image ===");
|
||||||
|
Builder::image_tag("alpine:latest", "my-alpine:v1.0")?;
|
||||||
|
println!("Image tagged successfully");
|
||||||
|
|
||||||
|
// Push an image (this would typically go to a real registry)
|
||||||
|
// println!("\n=== Pushing an image (example only) ===");
|
||||||
|
// println!("In a real scenario, you would push to a registry with:");
|
||||||
|
// println!("Builder::image_push(\"my-alpine:v1.0\", \"docker://registry.example.com/my-alpine:v1.0\", true)");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main function to run all examples
|
||||||
|
pub fn run_all_examples() -> Result<(), BuildahError> {
|
||||||
|
println!("=== BUILDAH MODULE EXAMPLES ===\n");
|
||||||
|
|
||||||
|
run_buildah_example()?;
|
||||||
|
build_image_example()?;
|
||||||
|
registry_operations_example()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _ = run_all_examples();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Rhai Example
|
||||||
|
|
||||||
|
```rhai
|
||||||
|
// rhaiexamples/04_buildah_operations.rhai
|
||||||
|
// Demonstrates container operations using SAL's buildah integration
|
||||||
|
// Note: This script requires buildah to be installed and may need root privileges
|
||||||
|
|
||||||
|
// Check if buildah is installed
|
||||||
|
let buildah_exists = which("buildah");
|
||||||
|
println(`Buildah exists: ${buildah_exists}`);
|
||||||
|
|
@ -4,29 +4,36 @@
|
|||||||
//! common container operations like creating containers, running commands,
|
//! common container operations like creating containers, running commands,
|
||||||
//! and managing images.
|
//! and managing images.
|
||||||
|
|
||||||
use sal::virt::buildah::{self, BuildahError};
|
use sal::virt::buildah::{BuildahError, Builder};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Run a complete buildah workflow example
|
/// Run a complete buildah workflow example
|
||||||
pub fn run_buildah_example() -> Result<(), BuildahError> {
|
pub fn run_buildah_example() -> Result<(), BuildahError> {
|
||||||
println!("Starting buildah example workflow...");
|
println!("Starting buildah example workflow...");
|
||||||
|
|
||||||
// Step 1: Create a container from an image
|
// Step 1: Create a container from an image using the Builder
|
||||||
println!("\n=== Creating container from fedora:latest ===");
|
println!("\n=== Creating container from fedora:latest ===");
|
||||||
let result = buildah::from("fedora:latest")?;
|
let mut builder = Builder::new("my-fedora-container", "fedora:latest")?;
|
||||||
let container_id = result.stdout.trim();
|
|
||||||
println!("Created container: {}", container_id);
|
// Reset the builder to remove any existing container
|
||||||
|
println!("\n=== Resetting the builder to start fresh ===");
|
||||||
|
builder.reset()?;
|
||||||
|
|
||||||
|
// Create a new container (or continue with existing one)
|
||||||
|
println!("\n=== Creating container from fedora:latest ===");
|
||||||
|
builder = Builder::new("my-fedora-container", "fedora:latest")?;
|
||||||
|
println!("Created container: {}", builder.container_id().unwrap_or(&"unknown".to_string()));
|
||||||
|
|
||||||
// Step 2: Run a command in the container
|
// Step 2: Run a command in the container
|
||||||
println!("\n=== Installing nginx in container ===");
|
println!("\n=== Installing nginx in container ===");
|
||||||
// Use chroot isolation to avoid BPF issues
|
// Use chroot isolation to avoid BPF issues
|
||||||
let install_result = buildah::bah_run_with_isolation(container_id, "dnf install -y nginx", "chroot")?;
|
let install_result = builder.run_with_isolation("dnf install -y nginx", "chroot")?;
|
||||||
println!("{:#?}", install_result);
|
println!("{:#?}", install_result);
|
||||||
println!("Installation output: {}", install_result.stdout);
|
println!("Installation output: {}", install_result.stdout);
|
||||||
|
|
||||||
// Step 3: Copy a file into the container
|
// Step 3: Copy a file into the container
|
||||||
println!("\n=== Copying configuration file to container ===");
|
println!("\n=== Copying configuration file to container ===");
|
||||||
buildah::bah_copy(container_id, "./example.conf", "/etc/example.conf").unwrap();
|
builder.copy("./example.conf", "/etc/example.conf")?;
|
||||||
|
|
||||||
// Step 4: Configure container metadata
|
// Step 4: Configure container metadata
|
||||||
println!("\n=== Configuring container metadata ===");
|
println!("\n=== Configuring container metadata ===");
|
||||||
@ -34,19 +41,18 @@ pub fn run_buildah_example() -> Result<(), BuildahError> {
|
|||||||
config_options.insert("port".to_string(), "80".to_string());
|
config_options.insert("port".to_string(), "80".to_string());
|
||||||
config_options.insert("label".to_string(), "maintainer=example@example.com".to_string());
|
config_options.insert("label".to_string(), "maintainer=example@example.com".to_string());
|
||||||
config_options.insert("entrypoint".to_string(), "/usr/sbin/nginx".to_string());
|
config_options.insert("entrypoint".to_string(), "/usr/sbin/nginx".to_string());
|
||||||
buildah::bah_config(container_id, config_options)?;
|
builder.config(config_options)?;
|
||||||
buildah::config(container_id, config_options)?;
|
|
||||||
println!("Container configured");
|
println!("Container configured");
|
||||||
|
|
||||||
// Step 5: Commit the container to create a new image
|
// Step 5: Commit the container to create a new image
|
||||||
println!("\n=== Committing container to create image ===");
|
println!("\n=== Committing container to create image ===");
|
||||||
let image_name = "my-nginx:latest";
|
let image_name = "my-nginx:latest";
|
||||||
buildah::image_commit(container_id, image_name, Some("docker"), true, true)?;
|
builder.commit(image_name)?;
|
||||||
println!("Created image: {}", image_name);
|
println!("Created image: {}", image_name);
|
||||||
|
|
||||||
// Step 6: List images to verify our new image exists
|
// Step 6: List images to verify our new image exists
|
||||||
println!("\n=== Listing images ===");
|
println!("\n=== Listing images ===");
|
||||||
let images = buildah::images()?;
|
let images = Builder::images()?;
|
||||||
println!("Found {} images:", images.len());
|
println!("Found {} images:", images.len());
|
||||||
for image in images {
|
for image in images {
|
||||||
println!(" ID: {}", image.id);
|
println!(" ID: {}", image.id);
|
||||||
@ -56,9 +62,10 @@ pub fn run_buildah_example() -> Result<(), BuildahError> {
|
|||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Step 7: Clean up (optional in a real workflow)
|
// Step 7: Clean up (optional in a real workflow)
|
||||||
println!("\n=== Cleaning up ===");
|
println!("\n=== Cleaning up ===");
|
||||||
buildah::image_remove(image_name).unwrap();
|
Builder::image_remove(image_name)?;
|
||||||
|
builder.remove()?;
|
||||||
|
|
||||||
println!("\nBuildah example workflow completed successfully!");
|
println!("\nBuildah example workflow completed successfully!");
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -69,7 +76,7 @@ pub fn build_image_example() -> Result<(), BuildahError> {
|
|||||||
println!("Building an image from a Containerfile...");
|
println!("Building an image from a Containerfile...");
|
||||||
|
|
||||||
// Use the build function with tag, context directory, and isolation to avoid BPF issues
|
// Use the build function with tag, context directory, and isolation to avoid BPF issues
|
||||||
let result = buildah::bah_build(Some("my-app:latest"), ".", "example_Dockerfile", Some("chroot"))?;
|
let result = Builder::build(Some("my-app:latest"), ".", "example_Dockerfile", Some("chroot"))?;
|
||||||
|
|
||||||
println!("Build output: {}", result.stdout);
|
println!("Build output: {}", result.stdout);
|
||||||
println!("Image built successfully!");
|
println!("Image built successfully!");
|
||||||
@ -83,18 +90,18 @@ pub fn registry_operations_example() -> Result<(), BuildahError> {
|
|||||||
|
|
||||||
// Pull an image
|
// Pull an image
|
||||||
println!("\n=== Pulling an image ===");
|
println!("\n=== Pulling an image ===");
|
||||||
buildah::image_pull("docker.io/library/alpine:latest", true)?;
|
Builder::image_pull("docker.io/library/alpine:latest", true)?;
|
||||||
println!("Image pulled successfully");
|
println!("Image pulled successfully");
|
||||||
|
|
||||||
// Tag the image
|
// Tag the image
|
||||||
println!("\n=== Tagging the image ===");
|
println!("\n=== Tagging the image ===");
|
||||||
buildah::image_tag("alpine:latest", "my-alpine:v1.0")?;
|
Builder::image_tag("alpine:latest", "my-alpine:v1.0")?;
|
||||||
println!("Image tagged successfully");
|
println!("Image tagged successfully");
|
||||||
|
|
||||||
// Push an image (this would typically go to a real registry)
|
// Push an image (this would typically go to a real registry)
|
||||||
// println!("\n=== Pushing an image (example only) ===");
|
// println!("\n=== Pushing an image (example only) ===");
|
||||||
// println!("In a real scenario, you would push to a registry with:");
|
// println!("In a real scenario, you would push to a registry with:");
|
||||||
// println!("buildah::image_push(\"my-alpine:v1.0\", \"docker://registry.example.com/my-alpine:v1.0\", true)");
|
// println!("Builder::image_push(\"my-alpine:v1.0\", \"docker://registry.example.com/my-alpine:v1.0\", true)");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
66
examples/rhai_git_example.rs
Normal file
66
examples/rhai_git_example.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
//! Example of using the Git module with Rhai
|
||||||
|
//!
|
||||||
|
//! This example demonstrates how to use the Git module functions
|
||||||
|
//! through the Rhai scripting language.
|
||||||
|
|
||||||
|
use sal::rhai::{self, Engine};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Create a new Rhai engine
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
// Register SAL functions with the engine
|
||||||
|
rhai::register(&mut engine)?;
|
||||||
|
|
||||||
|
// Run a Rhai script that uses Git functions
|
||||||
|
let script = r#"
|
||||||
|
// Print a header
|
||||||
|
print("=== Testing Git Module Functions ===\n");
|
||||||
|
|
||||||
|
// Test git_list function
|
||||||
|
print("Listing git repositories...");
|
||||||
|
let repos = git_list();
|
||||||
|
print(`Found ${repos.len()} repositories`);
|
||||||
|
|
||||||
|
// Print the first few repositories
|
||||||
|
if repos.len() > 0 {
|
||||||
|
print("First few repositories:");
|
||||||
|
let count = if repos.len() > 3 { 3 } else { repos.len() };
|
||||||
|
for i in range(0, count) {
|
||||||
|
print(` - ${repos[i]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test find_matching_repos function
|
||||||
|
if repos.len() > 0 {
|
||||||
|
print("\nTesting repository search...");
|
||||||
|
// Extract a part of the first repo name to search for
|
||||||
|
let repo_path = repos[0];
|
||||||
|
let parts = repo_path.split("/");
|
||||||
|
let repo_name = parts[parts.len() - 1];
|
||||||
|
|
||||||
|
print(`Searching for repositories containing "${repo_name}"`);
|
||||||
|
let matching = find_matching_repos(repo_name);
|
||||||
|
|
||||||
|
print(`Found ${matching.len()} matching repositories`);
|
||||||
|
for repo in matching {
|
||||||
|
print(` - ${repo}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a repository has changes
|
||||||
|
print("\nChecking for changes in repository...");
|
||||||
|
let has_changes = git_has_changes(repo_path);
|
||||||
|
print(`Repository ${repo_path} has changes: ${has_changes}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n=== Git Module Test Complete ===");
|
||||||
|
"#;
|
||||||
|
|
||||||
|
// Evaluate the script
|
||||||
|
match engine.eval::<()>(script) {
|
||||||
|
Ok(_) => println!("Script executed successfully"),
|
||||||
|
Err(e) => eprintln!("Script execution error: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -6,12 +6,30 @@
|
|||||||
let buildah_exists = which("buildah");
|
let buildah_exists = which("buildah");
|
||||||
println(`Buildah exists: ${buildah_exists}`);
|
println(`Buildah exists: ${buildah_exists}`);
|
||||||
|
|
||||||
|
// Create a builder object
|
||||||
|
println("\nCreating a builder object:");
|
||||||
|
let container_name = "my-container-example";
|
||||||
|
|
||||||
|
// Create a new builder
|
||||||
|
let builder = bah_new(container_name, "alpine:latest");
|
||||||
|
|
||||||
|
// Reset the builder to remove any existing container
|
||||||
|
println("\nResetting the builder to start fresh:");
|
||||||
|
let reset_result = builder.reset();
|
||||||
|
println(`Reset result: ${reset_result}`);
|
||||||
|
|
||||||
|
// Create a new container after reset
|
||||||
|
println("\nCreating a new container after reset:");
|
||||||
|
builder = bah_new(container_name, "alpine:latest");
|
||||||
|
println(`Container created with ID: ${builder.container_id}`);
|
||||||
|
println(`Builder created with name: ${builder.name}, image: ${builder.image}`);
|
||||||
|
|
||||||
// List available images (only if buildah is installed)
|
// List available images (only if buildah is installed)
|
||||||
println("Listing available container images:");
|
println("\nListing available container images:");
|
||||||
// if ! buildah_exists != "" {
|
// if ! buildah_exists != "" {
|
||||||
// //EXIT
|
// //EXIT
|
||||||
// }
|
// }
|
||||||
let images = bah_images();
|
let images = builder.images();
|
||||||
println(`Found ${images.len()} images`);
|
println(`Found ${images.len()} images`);
|
||||||
|
|
||||||
// Print image details (limited to 3)
|
// Print image details (limited to 3)
|
||||||
@ -24,73 +42,65 @@ for img in images {
|
|||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create a container from an image
|
|
||||||
println("\nCreating a container from alpine image:");
|
|
||||||
let container = bah_from("alpine:latest");
|
|
||||||
println(`Container result: success=${container.success}, code=${container.code}`);
|
|
||||||
println(`Container stdout: "${container.stdout}"`);
|
|
||||||
println(`Container stderr: "${container.stderr}"`);
|
|
||||||
let container_id = container.stdout;
|
|
||||||
println(`Container ID: ${container_id}`);
|
|
||||||
|
|
||||||
//Run a command in the container
|
//Run a command in the container
|
||||||
println("\nRunning a command in the container:");
|
println("\nRunning a command in the container:");
|
||||||
let run_result = bah_run(container_id, "echo 'Hello from container'");
|
let run_result = builder.run("echo 'Hello from container'");
|
||||||
println(`Command output: ${run_result.stdout}`);
|
println(`Command output: ${run_result.stdout}`);
|
||||||
|
|
||||||
//Add a file to the container
|
//Add a file to the container
|
||||||
println("\nAdding a file to the container:");
|
println("\nAdding a file to the container:");
|
||||||
let test_file = "test_file.txt";
|
let test_file = "test_file.txt";
|
||||||
run(`echo "Test content" > ${test_file}`);
|
// Create the test file using Rhai's file_write function
|
||||||
let add_result = bah_add(container_id, test_file, "/");
|
file_write(test_file, "Test content");
|
||||||
|
println(`Created test file: ${test_file}`);
|
||||||
|
println(`Created test file: ${test_file}`);
|
||||||
|
let add_result = builder.add(test_file, "/");
|
||||||
println(`Add result: ${add_result.success}`);
|
println(`Add result: ${add_result.success}`);
|
||||||
|
|
||||||
//Commit the container to create a new image
|
//Commit the container to create a new image
|
||||||
println("\nCommitting the container to create a new image:");
|
println("\nCommitting the container to create a new image:");
|
||||||
let commit_result = bah_commit(container_id, "my-custom-image:latest");
|
let commit_result = builder.commit("my-custom-image:latest");
|
||||||
println(`Commit result: ${commit_result.success}`);
|
println(`Commit result: ${commit_result.success}`);
|
||||||
|
|
||||||
//Remove the container
|
//Remove the container
|
||||||
println("\nRemoving the container:");
|
println("\nRemoving the container:");
|
||||||
let remove_result = bah_remove(container_id);
|
let remove_result = builder.remove();
|
||||||
println(`Remove result: ${remove_result.success}`);
|
println(`Remove result: ${remove_result.success}`);
|
||||||
|
|
||||||
//Clean up the test file
|
//Clean up the test file
|
||||||
delete(test_file);
|
delete(test_file);
|
||||||
|
|
||||||
// Demonstrate build options
|
// Demonstrate static methods
|
||||||
println("\nDemonstrating build options:");
|
println("\nDemonstrating static methods:");
|
||||||
let build_options = bah_new_build_options();
|
println("Building an image from a Dockerfile:");
|
||||||
build_options.tag = "example-image:latest";
|
let build_result = builder.build("example-image:latest", ".", "example_Dockerfile", "chroot");
|
||||||
build_options.context_dir = ".";
|
println(`Build result: ${build_result.success}`);
|
||||||
build_options.file = "example_Dockerfile";
|
|
||||||
|
|
||||||
println("Build options configured:");
|
// Pull an image
|
||||||
println(` - Tag: ${build_options.tag}`);
|
println("\nPulling an image:");
|
||||||
println(` - Context: ${build_options.context_dir}`);
|
let pull_result = builder.image_pull("alpine:latest", true);
|
||||||
println(` - Dockerfile: ${build_options.file}`);
|
println(`Pull result: ${pull_result.success}`);
|
||||||
|
|
||||||
// Demonstrate commit options
|
// Skip commit options demonstration since we removed the legacy functions
|
||||||
println("\nDemonstrating commit options:");
|
println("\nSkipping commit options demonstration (legacy functions removed)");
|
||||||
let commit_options = bah_new_commit_options();
|
|
||||||
commit_options.format = "docker";
|
|
||||||
commit_options.squash = true;
|
|
||||||
commit_options.rm = true;
|
|
||||||
|
|
||||||
println("Commit options configured:");
|
// Demonstrate config method
|
||||||
println(` - Format: ${commit_options.format}`);
|
println("\nDemonstrating config method:");
|
||||||
println(` - Squash: ${commit_options.squash}`);
|
// Create a new container for config demonstration
|
||||||
println(` - Remove container: ${commit_options.rm}`);
|
println("Creating a new container for config demonstration:");
|
||||||
|
builder = bah_new("config-demo-container", "alpine:latest");
|
||||||
|
println(`Container created with ID: ${builder.container_id}`);
|
||||||
|
|
||||||
// Demonstrate config options
|
let config_options = #{
|
||||||
println("\nDemonstrating config options:");
|
"author": "Rhai Example",
|
||||||
let config_options = bah_new_config_options();
|
"cmd": "/bin/sh -c 'echo Hello from Buildah'"
|
||||||
config_options.author = "Rhai Example";
|
};
|
||||||
config_options.cmd = "/bin/sh -c 'echo Hello from Buildah'";
|
let config_result = builder.config(config_options);
|
||||||
|
println(`Config result: ${config_result.success}`);
|
||||||
|
|
||||||
println("Config options configured:");
|
// Clean up the container
|
||||||
println(` - Author: ${config_options.author}`);
|
println("Removing the config demo container:");
|
||||||
println(` - Command: ${config_options.cmd}`);
|
builder.remove();
|
||||||
|
|
||||||
|
|
||||||
"Buildah operations script completed successfully!"
|
"Buildah operations script completed successfully!"
|
@ -2,6 +2,69 @@
|
|||||||
|
|
||||||
The Buildah module provides functions for working with containers and images using the Buildah tool. Buildah helps you create and manage container images.
|
The Buildah module provides functions for working with containers and images using the Buildah tool. Buildah helps you create and manage container images.
|
||||||
|
|
||||||
|
## Builder Pattern
|
||||||
|
|
||||||
|
The Buildah module now supports a Builder pattern, which provides a more intuitive and flexible way to work with containers and images.
|
||||||
|
|
||||||
|
### Creating a Builder
|
||||||
|
|
||||||
|
```rhai
|
||||||
|
// Create a builder with a name and base image
|
||||||
|
let builder = bah_new("my-container", "alpine:latest");
|
||||||
|
|
||||||
|
// Access builder properties
|
||||||
|
let container_id = builder.container_id;
|
||||||
|
let name = builder.name;
|
||||||
|
let image = builder.image;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Builder Methods
|
||||||
|
|
||||||
|
The Builder object provides the following methods:
|
||||||
|
|
||||||
|
- `run(command)`: Run a command in the container
|
||||||
|
- `run_with_isolation(command, isolation)`: Run a command with specified isolation
|
||||||
|
- `copy(source, dest)`: Copy files into the container
|
||||||
|
- `add(source, dest)`: Add files into the container
|
||||||
|
- `commit(image_name)`: Commit the container to an image
|
||||||
|
- `remove()`: Remove the container
|
||||||
|
- `reset()`: Remove the container and clear the container_id
|
||||||
|
- `config(options)`: Configure container metadata
|
||||||
|
- `images()`: List images in local storage
|
||||||
|
- `image_remove(image)`: Remove an image
|
||||||
|
- `image_pull(image, tls_verify)`: Pull an image from a registry
|
||||||
|
- `image_push(image, destination, tls_verify)`: Push an image to a registry
|
||||||
|
- `image_tag(image, new_name)`: Add a tag to an image
|
||||||
|
- `build(tag, context_dir, file, isolation)`: Build an image from a Dockerfile
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```rhai
|
||||||
|
// Create a builder
|
||||||
|
let builder = bah_new("my-container", "alpine:latest");
|
||||||
|
|
||||||
|
// Reset the builder to remove any existing container
|
||||||
|
builder.reset();
|
||||||
|
|
||||||
|
// Create a new container
|
||||||
|
builder = bah_new("my-container", "alpine:latest");
|
||||||
|
|
||||||
|
// Run a command
|
||||||
|
let result = builder.run("echo 'Hello from container'");
|
||||||
|
println(`Command output: ${result.stdout}`);
|
||||||
|
|
||||||
|
// Add a file
|
||||||
|
file_write("test_file.txt", "Test content");
|
||||||
|
builder.add("test_file.txt", "/");
|
||||||
|
|
||||||
|
// Commit to an image
|
||||||
|
builder.commit("my-custom-image:latest");
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
builder.remove();
|
||||||
|
delete("test_file.txt");
|
||||||
|
```
|
||||||
|
|
||||||
## Image Information
|
## Image Information
|
||||||
|
|
||||||
### Image Properties
|
### Image Properties
|
||||||
@ -14,332 +77,42 @@ When working with images, you can access the following information:
|
|||||||
- `size`: The size of the image
|
- `size`: The size of the image
|
||||||
- `created`: When the image was created
|
- `created`: When the image was created
|
||||||
|
|
||||||
## Container Functions
|
## Builder Methods
|
||||||
|
|
||||||
### `bah_from(image)`
|
### `bah_new(name, image)`
|
||||||
|
|
||||||
Creates a container from an image.
|
Creates a new Builder object for working with a container.
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
|
- `name` (string): The name to give the container
|
||||||
- `image` (string): The name or ID of the image to create the container from
|
- `image` (string): The name or ID of the image to create the container from
|
||||||
|
|
||||||
**Returns:** The ID of the newly created container if successful.
|
**Returns:** A Builder object if successful.
|
||||||
|
|
||||||
**Example:**
|
|
||||||
|
|
||||||
```rhai
|
|
||||||
// Create a container from an image
|
|
||||||
let result = bah_from("alpine:latest");
|
|
||||||
let container_id = result.stdout;
|
|
||||||
print(`Created container: ${container_id}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_run(container, command)`
|
|
||||||
|
|
||||||
Runs a command in a container.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `container` (string): The container ID or name
|
|
||||||
- `command` (string): The command to run
|
|
||||||
|
|
||||||
**Returns:** The output of the command if successful.
|
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```rhai
|
```rhai
|
||||||
// Run a command in a container
|
// Create a new Builder
|
||||||
let result = bah_run("my-container", "echo 'Hello from container'");
|
let builder = bah_new("my-container", "alpine:latest");
|
||||||
print(result.stdout);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### `bah_run_with_isolation(container, command, isolation)`
|
**Notes:**
|
||||||
|
- If a container with the given name already exists, it will be reused instead of creating a new one
|
||||||
|
- The Builder object provides methods for working with the container
|
||||||
|
|
||||||
Runs a command in a container with specified isolation.
|
### `reset()`
|
||||||
|
|
||||||
**Parameters:**
|
Resets a Builder by removing the container and clearing the container_id. This allows you to start fresh with the same Builder object.
|
||||||
- `container` (string): The container ID or name
|
|
||||||
- `command` (string): The command to run
|
|
||||||
- `isolation` (string): The isolation type (e.g., "chroot", "rootless", "oci")
|
|
||||||
|
|
||||||
**Returns:** The output of the command if successful.
|
**Returns:** Nothing.
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```rhai
|
```rhai
|
||||||
// Run a command with specific isolation
|
// Create a Builder
|
||||||
let result = bah_run_with_isolation("my-container", "ls -la", "chroot");
|
let builder = bah_new("my-container", "alpine:latest");
|
||||||
print(result.stdout);
|
|
||||||
```
|
// Reset the Builder to remove the container
|
||||||
|
builder.reset();
|
||||||
### `bah_copy(container, source, dest)`
|
|
||||||
|
// Create a new container with the same name
|
||||||
Copies files into a container.
|
builder = bah_new("my-container", "alpine:latest");
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `container` (string): The container ID or name
|
|
||||||
- `source` (string): The source path on the host
|
|
||||||
- `dest` (string): The destination path in the container
|
|
||||||
|
|
||||||
**Returns:** A success message if the copy operation worked.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Copy a file into a container
|
|
||||||
bah_copy("my-container", "./app.js", "/app/app.js");
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_add(container, source, dest)`
|
|
||||||
|
|
||||||
Adds files into a container. Similar to `bah_copy` but can also handle remote URLs.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `container` (string): The container ID or name
|
|
||||||
- `source` (string): The source path on the host or a URL
|
|
||||||
- `dest` (string): The destination path in the container
|
|
||||||
|
|
||||||
**Returns:** A success message if the add operation worked.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Add a file from a URL into a container
|
|
||||||
bah_add("my-container", "https://example.com/file.tar.gz", "/app/");
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_commit(container, image_name)`
|
|
||||||
|
|
||||||
Commits a container to an image.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `container` (string): The container ID or name
|
|
||||||
- `image_name` (string): The name to give the new image
|
|
||||||
|
|
||||||
**Returns:** A success message if the commit operation worked.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Commit a container to an image
|
|
||||||
bah_commit("my-container", "my-image:latest");
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_remove(container)`
|
|
||||||
|
|
||||||
Removes a container.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `container` (string): The container ID or name
|
|
||||||
|
|
||||||
**Returns:** A success message if the container was removed.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Remove a container
|
|
||||||
bah_remove("my-container");
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_list()`
|
|
||||||
|
|
||||||
Lists containers.
|
|
||||||
|
|
||||||
**Returns:** A list of containers if successful.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// List containers
|
|
||||||
let result = bah_list();
|
|
||||||
print(result.stdout);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_new_build_options()`
|
|
||||||
|
|
||||||
Creates a new map with default build options.
|
|
||||||
|
|
||||||
**Returns:** A map with the following default options:
|
|
||||||
- `tag` (unit/null): The tag for the image (default: null)
|
|
||||||
- `context_dir` (string): The build context directory (default: ".")
|
|
||||||
- `file` (string): The Dockerfile path (default: "Dockerfile")
|
|
||||||
- `isolation` (unit/null): The isolation type (default: null)
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Create build options
|
|
||||||
let options = bah_new_build_options();
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_build(options)`
|
|
||||||
|
|
||||||
Builds an image with options specified in a map.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `options` (map): A map of options created with `bah_new_build_options()`
|
|
||||||
|
|
||||||
**Returns:** A success message if the build operation worked.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Create and customize build options
|
|
||||||
let options = bah_new_build_options();
|
|
||||||
options.tag = "my-image:latest";
|
|
||||||
options.context_dir = "./app";
|
|
||||||
options.file = "Dockerfile.prod";
|
|
||||||
options.isolation = "chroot";
|
|
||||||
|
|
||||||
// Build an image with options
|
|
||||||
let result = bah_build(options);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Image Functions
|
|
||||||
|
|
||||||
### `bah_images()`
|
|
||||||
|
|
||||||
Lists images in local storage.
|
|
||||||
|
|
||||||
**Returns:** A list of images if successful.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// List images
|
|
||||||
let images = bah_images();
|
|
||||||
|
|
||||||
// Display image information
|
|
||||||
for image in images {
|
|
||||||
print(`ID: ${image.id}, Name: ${image.name}, Size: ${image.size}, Created: ${image.created}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### `bah_image_remove(image)`
|
|
||||||
|
|
||||||
Removes one or more images.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `image` (string): The image ID or name
|
|
||||||
|
|
||||||
**Returns:** A success message if the image was removed.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Remove an image
|
|
||||||
bah_image_remove("my-image:latest");
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_image_push(image, destination, tls_verify)`
|
|
||||||
|
|
||||||
Pushes an image to a registry.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `image` (string): The image ID or name
|
|
||||||
- `destination` (string): The destination registry/repository
|
|
||||||
- `tls_verify` (boolean): Whether to verify TLS certificates
|
|
||||||
|
|
||||||
**Returns:** A success message if the image was pushed.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Push an image to a registry
|
|
||||||
bah_image_push("my-image:latest", "registry.example.com/my-repo/my-image:latest", true);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_image_tag(image, new_name)`
|
|
||||||
|
|
||||||
Adds an additional name to a local image.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `image` (string): The image ID or name
|
|
||||||
- `new_name` (string): The new name to add
|
|
||||||
|
|
||||||
**Returns:** A success message if the image was tagged.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Tag an image with a new name
|
|
||||||
bah_image_tag("my-image:latest", "my-image:v1.0");
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_image_pull(image, tls_verify)`
|
|
||||||
|
|
||||||
Pulls an image from a registry.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `image` (string): The image to pull
|
|
||||||
- `tls_verify` (boolean): Whether to verify TLS certificates
|
|
||||||
|
|
||||||
**Returns:** A success message if the image was pulled.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Pull an image from a registry
|
|
||||||
bah_image_pull("alpine:latest", true);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_new_commit_options()`
|
|
||||||
|
|
||||||
Creates a new map with default commit options.
|
|
||||||
|
|
||||||
**Returns:** A map with the following default options:
|
|
||||||
- `format` (unit/null): The format of the image (default: null)
|
|
||||||
- `squash` (boolean): Whether to squash layers (default: false)
|
|
||||||
- `rm` (boolean): Whether to remove the container after commit (default: false)
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Create commit options
|
|
||||||
let options = bah_new_commit_options();
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_image_commit(container, image_name, options)`
|
|
||||||
|
|
||||||
Commits a container to an image with options specified in a map.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `container` (string): The container ID or name
|
|
||||||
- `image_name` (string): The name to give the new image
|
|
||||||
- `options` (map): A map of options created with `bah_new_commit_options()`
|
|
||||||
|
|
||||||
**Returns:** A success message if the image was created.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Create and customize commit options
|
|
||||||
let options = bah_new_commit_options();
|
|
||||||
options.format = "docker";
|
|
||||||
options.squash = true;
|
|
||||||
options.rm = true;
|
|
||||||
|
|
||||||
// Commit a container to an image with options
|
|
||||||
let result = bah_image_commit("my-container", "my-image:latest", options);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_new_config_options()`
|
|
||||||
|
|
||||||
Creates a new map for config options.
|
|
||||||
|
|
||||||
**Returns:** An empty map to be filled with configuration options.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Create config options
|
|
||||||
let options = bah_new_config_options();
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bah_config(container, options)`
|
|
||||||
|
|
||||||
Configures a container with options specified in a map.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `container` (string): The container ID or name
|
|
||||||
- `options` (map): A map of options created with `bah_new_config_options()`
|
|
||||||
|
|
||||||
**Returns:** A success message if the container was configured.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```rhai
|
|
||||||
// Create and customize config options
|
|
||||||
let options = bah_new_config_options();
|
|
||||||
options.author = "John Doe";
|
|
||||||
options.cmd = "echo Hello";
|
|
||||||
options.entrypoint = "/bin/sh -c";
|
|
||||||
options.workingdir = "/app";
|
|
||||||
options.env = "NODE_ENV=production";
|
|
||||||
options.label = "version=1.0";
|
|
||||||
|
|
||||||
// Configure a container with options
|
|
||||||
let result = bah_config("my-container", options);
|
|
||||||
```
|
```
|
||||||
|
@ -2,112 +2,202 @@
|
|||||||
|
|
||||||
This module provides Rhai wrappers for the Git functionality in SAL.
|
This module provides Rhai wrappers for the Git functionality in SAL.
|
||||||
|
|
||||||
## Basic Git Operations
|
> **Note:** The constructor for GitTree has been renamed from `new()` to `gittree_new()` to avoid confusion with other constructors. This makes the interface more explicit and less likely to cause naming conflicts.
|
||||||
|
|
||||||
### Clone a Repository
|
## Object-Oriented Design
|
||||||
|
|
||||||
|
The Git module follows an object-oriented design with two main classes:
|
||||||
|
|
||||||
|
1. **GitTree** - Represents a collection of git repositories under a base path
|
||||||
|
- Created with `gittree_new(base_path)`
|
||||||
|
- Methods for listing, finding, and getting repositories
|
||||||
|
|
||||||
|
2. **GitRepo** - Represents a single git repository
|
||||||
|
- Obtained from GitTree's `get()` method
|
||||||
|
- Methods for common git operations: pull, reset, push, commit
|
||||||
|
|
||||||
|
This design allows for a more intuitive and flexible interface, with method chaining for complex operations.
|
||||||
|
|
||||||
|
## Creating a GitTree
|
||||||
|
|
||||||
|
The GitTree object is the main entry point for git operations. It represents a collection of git repositories under a base path.
|
||||||
|
|
||||||
```rhai
|
```rhai
|
||||||
// Clone a repository to a standardized location in the user's home directory
|
// Create a new GitTree with a base path
|
||||||
let repo_path = git_clone("https://github.com/username/repo.git");
|
let git_tree = gittree_new("/root/code");
|
||||||
print(`Repository cloned to: ${repo_path}`);
|
print(`Created GitTree with base path: /home/user/code`);
|
||||||
```
|
```
|
||||||
|
|
||||||
### List Repositories
|
## Finding Repositories
|
||||||
|
|
||||||
|
### List All Repositories
|
||||||
|
|
||||||
```rhai
|
```rhai
|
||||||
// List all git repositories in the user's ~/code directory
|
// List all git repositories under the base path
|
||||||
let repos = git_list();
|
let repos = git_tree.list();
|
||||||
print("Found repositories:");
|
print(`Found ${repos.len()} repositories`);
|
||||||
|
|
||||||
|
// Print the repositories
|
||||||
for repo in repos {
|
for repo in repos {
|
||||||
print(` - ${repo}`);
|
print(` - ${repo}`);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Find Repositories
|
### Find Repositories Matching a Pattern
|
||||||
|
|
||||||
```rhai
|
```rhai
|
||||||
// Find repositories matching a pattern
|
// Find repositories matching a pattern
|
||||||
// Use a wildcard (*) suffix to find multiple matches
|
// Use a wildcard (*) suffix to find multiple matches
|
||||||
let matching_repos = find_matching_repos("my-project*");
|
let matching_repos = git_tree.find("my-project*");
|
||||||
print("Matching repositories:");
|
print("Matching repositories:");
|
||||||
for repo in matching_repos {
|
for repo in matching_repos {
|
||||||
print(` - ${repo}`);
|
print(` - ${repo}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a specific repository (must match exactly one)
|
// Find a specific repository (must match exactly one)
|
||||||
let specific_repo = find_matching_repos("unique-project")[0];
|
let specific_repo = git_tree.find("unique-project")[0];
|
||||||
print(`Found specific repository: ${specific_repo}`);
|
print(`Found specific repository: ${specific_repo}`);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Working with Repositories
|
||||||
|
|
||||||
|
### Get Repository Objects
|
||||||
|
|
||||||
|
```rhai
|
||||||
|
// Get GitRepo objects for repositories matching a pattern
|
||||||
|
let repos = git_tree.get("my-project*");
|
||||||
|
print(`Found ${repos.len()} repositories`);
|
||||||
|
|
||||||
|
// Get a specific repository
|
||||||
|
let repo = git_tree.get("unique-project")[0];
|
||||||
|
print(`Working with repository: ${repo.path()}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clone a Repository
|
||||||
|
|
||||||
|
```rhai
|
||||||
|
// Clone a repository by URL
|
||||||
|
// This will clone the repository to the base path of the GitTree
|
||||||
|
let repos = git_tree.get("https://github.com/username/repo.git");
|
||||||
|
let repo = repos[0];
|
||||||
|
print(`Repository cloned to: ${repo.path()}`);
|
||||||
|
```
|
||||||
|
|
||||||
### Check for Changes
|
### Check for Changes
|
||||||
|
|
||||||
```rhai
|
```rhai
|
||||||
// Check if a repository has uncommitted changes
|
// Check if a repository has uncommitted changes
|
||||||
let repo_path = "/path/to/repo";
|
let repo = git_tree.get("my-project")[0];
|
||||||
if git_has_changes(repo_path) {
|
if repo.has_changes() {
|
||||||
print("Repository has uncommitted changes");
|
print("Repository has uncommitted changes");
|
||||||
} else {
|
} else {
|
||||||
print("Repository is clean");
|
print("Repository is clean");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Repository Updates
|
## Repository Operations
|
||||||
|
|
||||||
### Update a Repository
|
### Pull Changes
|
||||||
|
|
||||||
```rhai
|
```rhai
|
||||||
// Update a repository by pulling the latest changes
|
// Pull the latest changes from the remote
|
||||||
// This will fail if there are uncommitted changes
|
// This will fail if there are uncommitted changes
|
||||||
let result = git_update("my-project");
|
let repo = git_tree.get("my-project")[0];
|
||||||
print(result);
|
let result = repo.pull();
|
||||||
|
print("Repository updated successfully");
|
||||||
```
|
```
|
||||||
|
|
||||||
### Force Update a Repository
|
### Reset Local Changes
|
||||||
|
|
||||||
```rhai
|
```rhai
|
||||||
// Force update a repository by discarding local changes and pulling the latest changes
|
// Reset any local changes in the repository
|
||||||
let result = git_update_force("my-project");
|
let repo = git_tree.get("my-project")[0];
|
||||||
print(result);
|
let result = repo.reset();
|
||||||
|
print("Repository reset successfully");
|
||||||
```
|
```
|
||||||
|
|
||||||
### Commit and Update
|
### Commit Changes
|
||||||
|
|
||||||
```rhai
|
```rhai
|
||||||
// Commit changes in a repository and then update it by pulling the latest changes
|
// Commit changes in the repository
|
||||||
let result = git_update_commit("my-project", "Fix bug in login form");
|
let repo = git_tree.get("my-project")[0];
|
||||||
print(result);
|
let result = repo.commit("Fix bug in login form");
|
||||||
|
print("Changes committed successfully");
|
||||||
```
|
```
|
||||||
|
|
||||||
### Commit and Push
|
### Push Changes
|
||||||
|
|
||||||
```rhai
|
```rhai
|
||||||
// Commit changes in a repository and push them to the remote
|
// Push changes to the remote
|
||||||
let result = git_update_commit_push("my-project", "Add new feature");
|
let repo = git_tree.get("my-project")[0];
|
||||||
print(result);
|
let result = repo.push();
|
||||||
|
print("Changes pushed successfully");
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Method Chaining
|
||||||
|
|
||||||
|
The GitRepo methods can be chained together for more complex operations:
|
||||||
|
|
||||||
|
```rhai
|
||||||
|
// Commit changes and push them to the remote
|
||||||
|
let repo = git_tree.get("my-project")[0];
|
||||||
|
let result = repo.commit("Add new feature").push();
|
||||||
|
print("Changes committed and pushed successfully");
|
||||||
|
|
||||||
|
// Reset local changes, pull the latest changes, and commit new changes
|
||||||
|
let repo = git_tree.get("my-project")[0];
|
||||||
|
let result = repo.reset().pull().commit("Update dependencies");
|
||||||
|
print("Repository updated successfully");
|
||||||
|
```
|
||||||
|
|
||||||
## Complete Example
|
## Complete Example
|
||||||
|
|
||||||
```rhai
|
```rhai
|
||||||
|
// Create a new GitTree
|
||||||
|
let home_dir = env("HOME");
|
||||||
|
let git_tree = gittree_new(`${home_dir}/code`);
|
||||||
|
|
||||||
// Clone a repository
|
// Clone a repository
|
||||||
let repo_url = "https://github.com/username/example-repo.git";
|
let repos = git_tree.get("https://github.com/username/example-repo.git");
|
||||||
let repo_path = git_clone(repo_url);
|
let repo = repos[0];
|
||||||
print(`Cloned repository to: ${repo_path}`);
|
print(`Cloned repository to: ${repo.path()}`);
|
||||||
|
|
||||||
// Make some changes (using OS module functions)
|
// Make some changes (using OS module functions)
|
||||||
let file_path = `${repo_path}/README.md`;
|
let file_path = `${repo.path()}/README.md`;
|
||||||
let content = "# Example Repository\n\nThis is an example repository.";
|
let content = "# Example Repository\n\nThis is an example repository.";
|
||||||
write_file(file_path, content);
|
write_file(file_path, content);
|
||||||
|
|
||||||
// Commit and push the changes
|
// Commit and push the changes
|
||||||
let commit_message = "Update README.md";
|
let result = repo.commit("Update README.md").push();
|
||||||
let result = git_update_commit_push(repo_path, commit_message);
|
print("Changes committed and pushed successfully");
|
||||||
print(result);
|
|
||||||
|
|
||||||
// List all repositories
|
// List all repositories
|
||||||
let repos = git_list();
|
let all_repos = git_tree.list();
|
||||||
print("All repositories:");
|
print("All repositories:");
|
||||||
for repo in repos {
|
for repo_path in all_repos {
|
||||||
print(` - ${repo}`);
|
print(` - ${repo_path}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
All methods in the Git module return a Result type, which means they can either succeed or fail with an error. If an error occurs, it will be propagated to the Rhai script as a runtime error.
|
||||||
|
|
||||||
|
For example, if you try to clone a repository that doesn't exist:
|
||||||
|
|
||||||
|
```rhai
|
||||||
|
// Try to clone a non-existent repository
|
||||||
|
try {
|
||||||
|
let git_tree = gittree_new("/root/code");
|
||||||
|
let repos = git_tree.get("https://github.com/nonexistent/repo.git");
|
||||||
|
print("This will not be executed if the repository doesn't exist");
|
||||||
|
} catch(err) {
|
||||||
|
print(`Error: ${err}`); // Will print the error message from git
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Common errors include:
|
||||||
|
- Invalid URL
|
||||||
|
- Repository not found
|
||||||
|
- Authentication failure
|
||||||
|
- Network issues
|
||||||
|
- Local changes exist when trying to pull
|
||||||
|
@ -17,9 +17,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
// Print a header
|
// Print a header
|
||||||
print("=== Testing Git Module Functions ===\n");
|
print("=== Testing Git Module Functions ===\n");
|
||||||
|
|
||||||
// Test git_list function
|
// Create a new GitTree object
|
||||||
print("Listing git repositories...");
|
let home_dir = env("HOME");
|
||||||
let repos = git_list();
|
let git_tree = gittree_new(`${home_dir}/code`);
|
||||||
|
print(`Created GitTree with base path: ${home_dir}/code`);
|
||||||
|
|
||||||
|
// Test list method
|
||||||
|
print("\nListing git repositories...");
|
||||||
|
let repos = git_tree.list();
|
||||||
print(`Found ${repos.len()} repositories`);
|
print(`Found ${repos.len()} repositories`);
|
||||||
|
|
||||||
// Print the first few repositories
|
// Print the first few repositories
|
||||||
@ -31,7 +36,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test find_matching_repos function
|
// Test find method
|
||||||
if repos.len() > 0 {
|
if repos.len() > 0 {
|
||||||
print("\nTesting repository search...");
|
print("\nTesting repository search...");
|
||||||
// Extract a part of the first repo name to search for
|
// Extract a part of the first repo name to search for
|
||||||
@ -40,17 +45,35 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let repo_name = parts[parts.len() - 1];
|
let repo_name = parts[parts.len() - 1];
|
||||||
|
|
||||||
print(`Searching for repositories containing "${repo_name}"`);
|
print(`Searching for repositories containing "${repo_name}"`);
|
||||||
let matching = find_matching_repos(repo_name);
|
let matching = git_tree.find(repo_name + "*");
|
||||||
|
|
||||||
print(`Found ${matching.len()} matching repositories`);
|
print(`Found ${matching.len()} matching repositories`);
|
||||||
for repo in matching {
|
for repo in matching {
|
||||||
print(` - ${repo}`);
|
print(` - ${repo}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test get method
|
||||||
|
print("\nTesting get method...");
|
||||||
|
let git_repos = git_tree.get(repo_name);
|
||||||
|
print(`Found ${git_repos.len()} GitRepo objects`);
|
||||||
|
|
||||||
|
// Test GitRepo methods
|
||||||
|
if git_repos.len() > 0 {
|
||||||
|
let git_repo = git_repos[0];
|
||||||
|
print(`\nTesting GitRepo methods on: ${git_repo.path()}`);
|
||||||
|
|
||||||
// Check if a repository has changes
|
// Check if a repository has changes
|
||||||
print("\nChecking for changes in repository...");
|
print("Checking for changes in repository...");
|
||||||
let has_changes = git_has_changes(repo_path);
|
let has_changes = git_repo.has_changes();
|
||||||
print(`Repository ${repo_path} has changes: ${has_changes}`);
|
print(`Repository has changes: ${has_changes}`);
|
||||||
|
|
||||||
|
// Test method chaining (only if there are no changes to avoid errors)
|
||||||
|
if !has_changes {
|
||||||
|
print("\nTesting method chaining (pull)...");
|
||||||
|
let result = git_repo.pull();
|
||||||
|
print("Pull operation completed successfully");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print("\n=== Git Module Test Complete ===");
|
print("\n=== Git Module Test Complete ===");
|
||||||
|
@ -17,9 +17,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
// Print a header
|
// Print a header
|
||||||
print("=== Testing Git Module Functions ===\n");
|
print("=== Testing Git Module Functions ===\n");
|
||||||
|
|
||||||
// Test git_list function
|
// Create a new GitTree object
|
||||||
print("Listing git repositories...");
|
let home_dir = env("HOME");
|
||||||
let repos = git_list();
|
let git_tree = gittree_new(`${home_dir}/code`);
|
||||||
|
print(`Created GitTree with base path: ${home_dir}/code`);
|
||||||
|
|
||||||
|
// Test list method
|
||||||
|
print("\nListing git repositories...");
|
||||||
|
let repos = git_tree.list();
|
||||||
print(`Found ${repos.len()} repositories`);
|
print(`Found ${repos.len()} repositories`);
|
||||||
|
|
||||||
// Print the first few repositories
|
// Print the first few repositories
|
||||||
@ -31,7 +36,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test find_matching_repos function
|
// Test find method
|
||||||
if repos.len() > 0 {
|
if repos.len() > 0 {
|
||||||
print("\nTesting repository search...");
|
print("\nTesting repository search...");
|
||||||
// Extract a part of the first repo name to search for
|
// Extract a part of the first repo name to search for
|
||||||
@ -40,17 +45,35 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let repo_name = parts[parts.len() - 1];
|
let repo_name = parts[parts.len() - 1];
|
||||||
|
|
||||||
print(`Searching for repositories containing "${repo_name}"`);
|
print(`Searching for repositories containing "${repo_name}"`);
|
||||||
let matching = find_matching_repos(repo_name);
|
let matching = git_tree.find(repo_name + "*");
|
||||||
|
|
||||||
print(`Found ${matching.len()} matching repositories`);
|
print(`Found ${matching.len()} matching repositories`);
|
||||||
for repo in matching {
|
for repo in matching {
|
||||||
print(` - ${repo}`);
|
print(` - ${repo}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test get method
|
||||||
|
print("\nTesting get method...");
|
||||||
|
let git_repos = git_tree.get(repo_name);
|
||||||
|
print(`Found ${git_repos.len()} GitRepo objects`);
|
||||||
|
|
||||||
|
// Test GitRepo methods
|
||||||
|
if git_repos.len() > 0 {
|
||||||
|
let git_repo = git_repos[0];
|
||||||
|
print(`\nTesting GitRepo methods on: ${git_repo.path()}`);
|
||||||
|
|
||||||
// Check if a repository has changes
|
// Check if a repository has changes
|
||||||
print("\nChecking for changes in repository...");
|
print("Checking for changes in repository...");
|
||||||
let has_changes = git_has_changes(repo_path);
|
let has_changes = git_repo.has_changes();
|
||||||
print(`Repository ${repo_path} has changes: ${has_changes}`);
|
print(`Repository has changes: ${has_changes}`);
|
||||||
|
|
||||||
|
// Test method chaining (only if there are no changes to avoid errors)
|
||||||
|
if !has_changes {
|
||||||
|
print("\nTesting method chaining (pull)...");
|
||||||
|
let result = git_repo.pull();
|
||||||
|
print("Pull operation completed successfully");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print("\n=== Git Module Test Complete ===");
|
print("\n=== Git Module Test Complete ===");
|
||||||
|
27
src/examples/run_test_git.rs
Normal file
27
src/examples/run_test_git.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//! Example of running the test_git.rhai script
|
||||||
|
//!
|
||||||
|
//! This example demonstrates how to run the test_git.rhai script
|
||||||
|
//! through the Rhai scripting engine.
|
||||||
|
|
||||||
|
use sal::rhai::{self, Engine};
|
||||||
|
use std::fs;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
// Create a new Rhai engine
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
// Register SAL functions with the engine
|
||||||
|
rhai::register(&mut engine)?;
|
||||||
|
|
||||||
|
// Read the test script
|
||||||
|
let script = fs::read_to_string("src/test_git.rhai")?;
|
||||||
|
|
||||||
|
// Evaluate the script
|
||||||
|
match engine.eval::<()>(&script) {
|
||||||
|
Ok(_) => println!("Script executed successfully"),
|
||||||
|
Err(e) => eprintln!("Script execution error: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
27
src/examples/simple_git_test.rs
Normal file
27
src/examples/simple_git_test.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//! Simple example of using the Git module with Rhai
|
||||||
|
//!
|
||||||
|
//! This example demonstrates how to use the Git module functions
|
||||||
|
//! through the Rhai scripting language.
|
||||||
|
|
||||||
|
use sal::rhai::{self, Engine};
|
||||||
|
use std::fs;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
// Create a new Rhai engine
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
// Register SAL functions with the engine
|
||||||
|
rhai::register(&mut engine)?;
|
||||||
|
|
||||||
|
// Read the test script
|
||||||
|
let script = fs::read_to_string("simple_git_test.rhai")?;
|
||||||
|
|
||||||
|
// Evaluate the script
|
||||||
|
match engine.eval::<()>(&script) {
|
||||||
|
Ok(_) => println!("Script executed successfully"),
|
||||||
|
Err(e) => eprintln!("Script execution error: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
622
src/git/git.rs
622
src/git/git.rs
@ -11,6 +11,7 @@ use std::error::Error;
|
|||||||
pub enum GitError {
|
pub enum GitError {
|
||||||
GitNotInstalled(std::io::Error),
|
GitNotInstalled(std::io::Error),
|
||||||
InvalidUrl(String),
|
InvalidUrl(String),
|
||||||
|
InvalidBasePath(String),
|
||||||
HomeDirectoryNotFound(std::env::VarError),
|
HomeDirectoryNotFound(std::env::VarError),
|
||||||
FileSystemError(std::io::Error),
|
FileSystemError(std::io::Error),
|
||||||
GitCommandFailed(String),
|
GitCommandFailed(String),
|
||||||
@ -28,6 +29,7 @@ impl fmt::Display for GitError {
|
|||||||
match self {
|
match self {
|
||||||
GitError::GitNotInstalled(e) => write!(f, "Git is not installed: {}", e),
|
GitError::GitNotInstalled(e) => write!(f, "Git is not installed: {}", e),
|
||||||
GitError::InvalidUrl(url) => write!(f, "Could not parse git URL: {}", url),
|
GitError::InvalidUrl(url) => write!(f, "Could not parse git URL: {}", url),
|
||||||
|
GitError::InvalidBasePath(path) => write!(f, "Invalid base path: {}", path),
|
||||||
GitError::HomeDirectoryNotFound(e) => write!(f, "Could not determine home directory: {}", e),
|
GitError::HomeDirectoryNotFound(e) => write!(f, "Could not determine home directory: {}", e),
|
||||||
GitError::FileSystemError(e) => write!(f, "Error creating directory structure: {}", e),
|
GitError::FileSystemError(e) => write!(f, "Error creating directory structure: {}", e),
|
||||||
GitError::GitCommandFailed(e) => write!(f, "{}", e),
|
GitError::GitCommandFailed(e) => write!(f, "{}", e),
|
||||||
@ -55,98 +57,21 @@ impl Error for GitError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Git utility functions
|
/// Parses a git URL to extract the server, account, and repository name.
|
||||||
|
///
|
||||||
/**
|
/// # Arguments
|
||||||
* Clones a git repository to a standardized location in the user's home directory.
|
///
|
||||||
*
|
/// * `url` - The URL of the git repository to parse. Can be in HTTPS format
|
||||||
* # Arguments
|
/// (https://github.com/username/repo.git) or SSH format (git@github.com:username/repo.git).
|
||||||
*
|
///
|
||||||
* * `url` - The URL of the git repository to clone. Can be in HTTPS format
|
/// # Returns
|
||||||
* (https://github.com/username/repo.git) or SSH format (git@github.com:username/repo.git).
|
///
|
||||||
*
|
/// A tuple containing:
|
||||||
* # Returns
|
/// * `server` - The server name (e.g., "github.com")
|
||||||
*
|
/// * `account` - The account or organization name (e.g., "username")
|
||||||
* * `Ok(String)` - The path where the repository was cloned, formatted as
|
/// * `repo` - The repository name (e.g., "repo")
|
||||||
* ~/code/server/account/repo (e.g., ~/code/github.com/username/repo).
|
///
|
||||||
* * `Err(GitError)` - An error if the clone operation failed.
|
/// If the URL cannot be parsed, all three values will be empty strings.
|
||||||
*
|
|
||||||
* # Examples
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* let repo_path = git_clone("https://github.com/username/repo.git")?;
|
|
||||||
* println!("Repository cloned to: {}", repo_path);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
pub fn git_clone(url: &str) -> Result<String, GitError> {
|
|
||||||
// Check if git is installed
|
|
||||||
let _git_check = Command::new("git")
|
|
||||||
.arg("--version")
|
|
||||||
.output()
|
|
||||||
.map_err(GitError::GitNotInstalled)?;
|
|
||||||
|
|
||||||
// Parse the URL to determine the clone path
|
|
||||||
let (server, account, repo) = parse_git_url(url);
|
|
||||||
if server.is_empty() || account.is_empty() || repo.is_empty() {
|
|
||||||
return Err(GitError::InvalidUrl(url.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the target directory
|
|
||||||
let home_dir = env::var("HOME").map_err(GitError::HomeDirectoryNotFound)?;
|
|
||||||
|
|
||||||
let clone_path = format!("{}/code/{}/{}/{}", home_dir, server, account, repo);
|
|
||||||
let clone_dir = Path::new(&clone_path);
|
|
||||||
|
|
||||||
// Check if repo already exists
|
|
||||||
if clone_dir.exists() {
|
|
||||||
return Ok(format!("Repository already exists at {}", clone_path));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create parent directory
|
|
||||||
if let Some(parent) = clone_dir.parent() {
|
|
||||||
fs::create_dir_all(parent).map_err(GitError::FileSystemError)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone the repository
|
|
||||||
let output = Command::new("git")
|
|
||||||
.args(&["clone", "--depth", "1", url, &clone_path])
|
|
||||||
.output()
|
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
|
||||||
|
|
||||||
if output.status.success() {
|
|
||||||
Ok(clone_path)
|
|
||||||
} else {
|
|
||||||
let error = String::from_utf8_lossy(&output.stderr);
|
|
||||||
Err(GitError::GitCommandFailed(format!("Git clone error: {}", error)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a git URL to extract the server, account, and repository name.
|
|
||||||
*
|
|
||||||
* # Arguments
|
|
||||||
*
|
|
||||||
* * `url` - The URL of the git repository to parse. Can be in HTTPS format
|
|
||||||
* (https://github.com/username/repo.git) or SSH format (git@github.com:username/repo.git).
|
|
||||||
*
|
|
||||||
* # Returns
|
|
||||||
*
|
|
||||||
* A tuple containing:
|
|
||||||
* * `server` - The server name (e.g., "github.com")
|
|
||||||
* * `account` - The account or organization name (e.g., "username")
|
|
||||||
* * `repo` - The repository name (e.g., "repo")
|
|
||||||
*
|
|
||||||
* If the URL cannot be parsed, all three values will be empty strings.
|
|
||||||
*
|
|
||||||
* # Examples
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* let (server, account, repo) = parse_git_url("https://github.com/username/repo.git");
|
|
||||||
* assert_eq!(server, "github.com");
|
|
||||||
* assert_eq!(account, "username");
|
|
||||||
* assert_eq!(repo, "repo");
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
pub fn parse_git_url(url: &str) -> (String, String, String) {
|
pub fn parse_git_url(url: &str) -> (String, String, String) {
|
||||||
// HTTP(S) URL format: https://github.com/username/repo.git
|
// HTTP(S) URL format: https://github.com/username/repo.git
|
||||||
let https_re = Regex::new(r"https?://([^/]+)/([^/]+)/([^/\.]+)(?:\.git)?").unwrap();
|
let https_re = Regex::new(r"https?://([^/]+)/([^/]+)/([^/\.]+)(?:\.git)?").unwrap();
|
||||||
@ -171,34 +96,66 @@ pub fn parse_git_url(url: &str) -> (String, String, String) {
|
|||||||
(String::new(), String::new(), String::new())
|
(String::new(), String::new(), String::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Checks if git is installed on the system.
|
||||||
* Lists all git repositories found in the user's ~/code directory.
|
///
|
||||||
*
|
/// # Returns
|
||||||
* This function searches for directories containing a .git subdirectory,
|
///
|
||||||
* which indicates a git repository.
|
/// * `Ok(())` - If git is installed
|
||||||
*
|
/// * `Err(GitError)` - If git is not installed
|
||||||
* # Returns
|
fn check_git_installed() -> Result<(), GitError> {
|
||||||
*
|
Command::new("git")
|
||||||
* * `Ok(Vec<String>)` - A vector of paths to git repositories
|
.arg("--version")
|
||||||
* * `Err(GitError)` - An error if the operation failed
|
.output()
|
||||||
*
|
.map_err(GitError::GitNotInstalled)?;
|
||||||
* # Examples
|
Ok(())
|
||||||
*
|
}
|
||||||
* ```
|
|
||||||
* let repos = git_list()?;
|
|
||||||
* for repo in repos {
|
|
||||||
* println!("Found repository: {}", repo);
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
pub fn git_list() -> Result<Vec<String>, GitError> {
|
|
||||||
// Get home directory
|
|
||||||
let home_dir = env::var("HOME").map_err(GitError::HomeDirectoryNotFound)?;
|
|
||||||
|
|
||||||
let code_dir = format!("{}/code", home_dir);
|
/// Represents a collection of git repositories under a base path.
|
||||||
let code_path = Path::new(&code_dir);
|
#[derive(Clone)]
|
||||||
|
pub struct GitTree {
|
||||||
|
base_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
if !code_path.exists() || !code_path.is_dir() {
|
impl GitTree {
|
||||||
|
/// Creates a new GitTree with the specified base path.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `base_path` - The base path where all git repositories are located
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Ok(GitTree)` - A new GitTree instance
|
||||||
|
/// * `Err(GitError)` - If the base path is invalid or cannot be created
|
||||||
|
pub fn new(base_path: &str) -> Result<Self, GitError> {
|
||||||
|
// Check if git is installed
|
||||||
|
check_git_installed()?;
|
||||||
|
|
||||||
|
// Validate the base path
|
||||||
|
let path = Path::new(base_path);
|
||||||
|
if !path.exists() {
|
||||||
|
fs::create_dir_all(path).map_err(|e| {
|
||||||
|
GitError::FileSystemError(e)
|
||||||
|
})?;
|
||||||
|
} else if !path.is_dir() {
|
||||||
|
return Err(GitError::InvalidBasePath(base_path.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(GitTree {
|
||||||
|
base_path: base_path.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists all git repositories under the base path.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Ok(Vec<String>)` - A vector of paths to git repositories
|
||||||
|
/// * `Err(GitError)` - If the operation failed
|
||||||
|
pub fn list(&self) -> Result<Vec<String>, GitError> {
|
||||||
|
let base_path = Path::new(&self.base_path);
|
||||||
|
|
||||||
|
if !base_path.exists() || !base_path.is_dir() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +163,7 @@ pub fn git_list() -> Result<Vec<String>, GitError> {
|
|||||||
|
|
||||||
// Find all directories with .git subdirectories
|
// Find all directories with .git subdirectories
|
||||||
let output = Command::new("find")
|
let output = Command::new("find")
|
||||||
.args(&[&code_dir, "-type", "d", "-name", ".git"])
|
.args(&[&self.base_path, "-type", "d", "-name", ".git"])
|
||||||
.output()
|
.output()
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
.map_err(GitError::CommandExecutionError)?;
|
||||||
|
|
||||||
@ -228,65 +185,22 @@ pub fn git_list() -> Result<Vec<String>, GitError> {
|
|||||||
Ok(repos)
|
Ok(repos)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Finds repositories matching a pattern or partial path.
|
||||||
* Checks if a git repository has uncommitted changes.
|
///
|
||||||
*
|
/// # Arguments
|
||||||
* # Arguments
|
///
|
||||||
*
|
/// * `pattern` - The pattern to match against repository paths
|
||||||
* * `repo_path` - The path to the git repository
|
/// - If the pattern ends with '*', all matching repositories are returned
|
||||||
*
|
/// - Otherwise, exactly one matching repository must be found
|
||||||
* # Returns
|
///
|
||||||
*
|
/// # Returns
|
||||||
* * `Ok(bool)` - True if the repository has uncommitted changes, false otherwise
|
///
|
||||||
* * `Err(GitError)` - An error if the operation failed
|
/// * `Ok(Vec<String>)` - A vector of paths to matching repositories
|
||||||
*
|
/// * `Err(GitError)` - If no matching repositories are found,
|
||||||
* # Examples
|
/// or if multiple repositories match a non-wildcard pattern
|
||||||
*
|
pub fn find(&self, pattern: &str) -> Result<Vec<String>, GitError> {
|
||||||
* ```
|
|
||||||
* if has_git_changes("/path/to/repo")? {
|
|
||||||
* println!("Repository has uncommitted changes");
|
|
||||||
* } else {
|
|
||||||
* println!("Repository is clean");
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
pub fn has_git_changes(repo_path: &str) -> Result<bool, GitError> {
|
|
||||||
let output = Command::new("git")
|
|
||||||
.args(&["-C", repo_path, "status", "--porcelain"])
|
|
||||||
.output()
|
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
|
||||||
|
|
||||||
Ok(!output.stdout.is_empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds repositories matching a pattern or partial path.
|
|
||||||
*
|
|
||||||
* # Arguments
|
|
||||||
*
|
|
||||||
* * `pattern` - The pattern to match against repository paths
|
|
||||||
* - If the pattern ends with '*', all matching repositories are returned
|
|
||||||
* - Otherwise, exactly one matching repository must be found
|
|
||||||
*
|
|
||||||
* # Returns
|
|
||||||
*
|
|
||||||
* * `Ok(Vec<String>)` - A vector of paths to matching repositories
|
|
||||||
* * `Err(GitError)` - An error if no matching repositories are found,
|
|
||||||
* or if multiple repositories match a non-wildcard pattern
|
|
||||||
*
|
|
||||||
* # Examples
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* // Find all repositories containing "project"
|
|
||||||
* let repos = find_matching_repos("project*")?;
|
|
||||||
*
|
|
||||||
* // Find exactly one repository containing "unique-project"
|
|
||||||
* let repo = find_matching_repos("unique-project")?[0];
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
pub fn find_matching_repos(pattern: &str) -> Result<Vec<String>, GitError> {
|
|
||||||
// Get all repos
|
// Get all repos
|
||||||
let repos = git_list()?;
|
let repos = self.list()?;
|
||||||
|
|
||||||
if repos.is_empty() {
|
if repos.is_empty() {
|
||||||
return Err(GitError::NoRepositoriesFound);
|
return Err(GitError::NoRepositoriesFound);
|
||||||
@ -320,101 +234,154 @@ pub fn find_matching_repos(pattern: &str) -> Result<Vec<String>, GitError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Gets one or more GitRepo objects based on a path pattern or URL.
|
||||||
* Updates a git repository by pulling the latest changes.
|
///
|
||||||
*
|
/// # Arguments
|
||||||
* This function will fail if there are uncommitted changes in the repository.
|
///
|
||||||
*
|
/// * `path_or_url` - The path pattern to match against repository paths or a git URL
|
||||||
* # Arguments
|
/// - If it's a URL, the repository will be cloned if it doesn't exist
|
||||||
*
|
/// - If it's a path pattern, it will find matching repositories
|
||||||
* * `repo_path` - The path to the git repository, or a partial path that uniquely identifies a repository
|
///
|
||||||
*
|
/// # Returns
|
||||||
* # Returns
|
///
|
||||||
*
|
/// * `Ok(Vec<GitRepo>)` - A vector of GitRepo objects
|
||||||
* * `Ok(String)` - A success message indicating the repository was updated
|
/// * `Err(GitError)` - If no matching repositories are found or the clone operation failed
|
||||||
* * `Err(GitError)` - An error if the update failed
|
pub fn get(&self, path_or_url: &str) -> Result<Vec<GitRepo>, GitError> {
|
||||||
*
|
// Check if it's a URL
|
||||||
* # Examples
|
if path_or_url.starts_with("http") || path_or_url.starts_with("git@") {
|
||||||
*
|
// Parse the URL
|
||||||
* ```
|
let (server, account, repo) = parse_git_url(path_or_url);
|
||||||
* let result = git_update("my-project")?;
|
if server.is_empty() || account.is_empty() || repo.is_empty() {
|
||||||
* println!("{}", result); // "Successfully updated repository at /home/user/code/github.com/user/my-project"
|
return Err(GitError::InvalidUrl(path_or_url.to_string()));
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
pub fn git_update(repo_path: &str) -> Result<String, GitError> {
|
|
||||||
// If repo_path may be a partial path, find the matching repository
|
|
||||||
let repos = find_matching_repos(repo_path)?;
|
|
||||||
|
|
||||||
// Should only be one repository at this point
|
|
||||||
let actual_path = &repos[0];
|
|
||||||
|
|
||||||
// Check if repository exists and is a git repository
|
|
||||||
let git_dir = Path::new(actual_path).join(".git");
|
|
||||||
if !git_dir.exists() || !git_dir.is_dir() {
|
|
||||||
return Err(GitError::NotAGitRepository(actual_path.clone()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for local changes
|
// Create the target directory
|
||||||
if has_git_changes(actual_path)? {
|
let clone_path = format!("{}/{}/{}/{}", self.base_path, server, account, repo);
|
||||||
return Err(GitError::LocalChangesExist(actual_path.clone()));
|
let clone_dir = Path::new(&clone_path);
|
||||||
|
|
||||||
|
// Check if repo already exists
|
||||||
|
if clone_dir.exists() {
|
||||||
|
return Ok(vec![GitRepo::new(clone_path)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull the latest changes
|
// Create parent directory
|
||||||
|
if let Some(parent) = clone_dir.parent() {
|
||||||
|
fs::create_dir_all(parent).map_err(GitError::FileSystemError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone the repository
|
||||||
let output = Command::new("git")
|
let output = Command::new("git")
|
||||||
.args(&["-C", actual_path, "pull"])
|
.args(&["clone", "--depth", "1", path_or_url, &clone_path])
|
||||||
.output()
|
.output()
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
.map_err(GitError::CommandExecutionError)?;
|
||||||
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
Ok(vec![GitRepo::new(clone_path)])
|
||||||
if stdout.contains("Already up to date") {
|
|
||||||
Ok(format!("Repository already up to date at {}", actual_path))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(format!("Successfully updated repository at {}", actual_path))
|
let error = String::from_utf8_lossy(&output.stderr);
|
||||||
|
Err(GitError::GitCommandFailed(format!("Git clone error: {}", error)))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// It's a path pattern, find matching repositories
|
||||||
|
let repo_paths = self.find(path_or_url)?;
|
||||||
|
|
||||||
|
// Convert paths to GitRepo objects
|
||||||
|
let repos: Vec<GitRepo> = repo_paths.into_iter()
|
||||||
|
.map(GitRepo::new)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(repos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a git repository.
|
||||||
|
pub struct GitRepo {
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GitRepo {
|
||||||
|
/// Creates a new GitRepo with the specified path.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `path` - The path to the git repository
|
||||||
|
pub fn new(path: String) -> Self {
|
||||||
|
GitRepo { path }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the path of the repository.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * The path to the git repository
|
||||||
|
pub fn path(&self) -> &str {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the repository has uncommitted changes.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Ok(bool)` - True if the repository has uncommitted changes, false otherwise
|
||||||
|
/// * `Err(GitError)` - If the operation failed
|
||||||
|
pub fn has_changes(&self) -> Result<bool, GitError> {
|
||||||
|
let output = Command::new("git")
|
||||||
|
.args(&["-C", &self.path, "status", "--porcelain"])
|
||||||
|
.output()
|
||||||
|
.map_err(GitError::CommandExecutionError)?;
|
||||||
|
|
||||||
|
Ok(!output.stdout.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pulls the latest changes from the remote repository.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Ok(Self)` - The GitRepo object for method chaining
|
||||||
|
/// * `Err(GitError)` - If the pull operation failed
|
||||||
|
pub fn pull(&self) -> Result<Self, GitError> {
|
||||||
|
// Check if repository exists and is a git repository
|
||||||
|
let git_dir = Path::new(&self.path).join(".git");
|
||||||
|
if !git_dir.exists() || !git_dir.is_dir() {
|
||||||
|
return Err(GitError::NotAGitRepository(self.path.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for local changes
|
||||||
|
if self.has_changes()? {
|
||||||
|
return Err(GitError::LocalChangesExist(self.path.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull the latest changes
|
||||||
|
let output = Command::new("git")
|
||||||
|
.args(&["-C", &self.path, "pull"])
|
||||||
|
.output()
|
||||||
|
.map_err(GitError::CommandExecutionError)?;
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
Ok(self.clone())
|
||||||
} else {
|
} else {
|
||||||
let error = String::from_utf8_lossy(&output.stderr);
|
let error = String::from_utf8_lossy(&output.stderr);
|
||||||
Err(GitError::GitCommandFailed(format!("Git pull error: {}", error)))
|
Err(GitError::GitCommandFailed(format!("Git pull error: {}", error)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Resets any local changes in the repository.
|
||||||
* Force updates a git repository by discarding local changes and pulling the latest changes.
|
///
|
||||||
*
|
/// # Returns
|
||||||
* This function will reset any uncommitted changes and clean untracked files before pulling.
|
///
|
||||||
*
|
/// * `Ok(Self)` - The GitRepo object for method chaining
|
||||||
* # Arguments
|
/// * `Err(GitError)` - If the reset operation failed
|
||||||
*
|
pub fn reset(&self) -> Result<Self, GitError> {
|
||||||
* * `repo_path` - The path to the git repository, or a partial path that uniquely identifies a repository
|
|
||||||
*
|
|
||||||
* # Returns
|
|
||||||
*
|
|
||||||
* * `Ok(String)` - A success message indicating the repository was force-updated
|
|
||||||
* * `Err(GitError)` - An error if the update failed
|
|
||||||
*
|
|
||||||
* # Examples
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* let result = git_update_force("my-project")?;
|
|
||||||
* println!("{}", result); // "Successfully force-updated repository at /home/user/code/github.com/user/my-project"
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
pub fn git_update_force(repo_path: &str) -> Result<String, GitError> {
|
|
||||||
// If repo_path may be a partial path, find the matching repository
|
|
||||||
let repos = find_matching_repos(repo_path)?;
|
|
||||||
|
|
||||||
// Should only be one repository at this point
|
|
||||||
let actual_path = &repos[0];
|
|
||||||
|
|
||||||
// Check if repository exists and is a git repository
|
// Check if repository exists and is a git repository
|
||||||
let git_dir = Path::new(actual_path).join(".git");
|
let git_dir = Path::new(&self.path).join(".git");
|
||||||
if !git_dir.exists() || !git_dir.is_dir() {
|
if !git_dir.exists() || !git_dir.is_dir() {
|
||||||
return Err(GitError::NotAGitRepository(actual_path.clone()));
|
return Err(GitError::NotAGitRepository(self.path.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset any local changes
|
// Reset any local changes
|
||||||
let reset_output = Command::new("git")
|
let reset_output = Command::new("git")
|
||||||
.args(&["-C", actual_path, "reset", "--hard", "HEAD"])
|
.args(&["-C", &self.path, "reset", "--hard", "HEAD"])
|
||||||
.output()
|
.output()
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
.map_err(GitError::CommandExecutionError)?;
|
||||||
|
|
||||||
@ -425,7 +392,7 @@ pub fn git_update_force(repo_path: &str) -> Result<String, GitError> {
|
|||||||
|
|
||||||
// Clean untracked files
|
// Clean untracked files
|
||||||
let clean_output = Command::new("git")
|
let clean_output = Command::new("git")
|
||||||
.args(&["-C", actual_path, "clean", "-fd"])
|
.args(&["-C", &self.path, "clean", "-fd"])
|
||||||
.output()
|
.output()
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
.map_err(GitError::CommandExecutionError)?;
|
||||||
|
|
||||||
@ -434,61 +401,34 @@ pub fn git_update_force(repo_path: &str) -> Result<String, GitError> {
|
|||||||
return Err(GitError::GitCommandFailed(format!("Git clean error: {}", error)));
|
return Err(GitError::GitCommandFailed(format!("Git clean error: {}", error)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull the latest changes
|
Ok(self.clone())
|
||||||
let pull_output = Command::new("git")
|
|
||||||
.args(&["-C", actual_path, "pull"])
|
|
||||||
.output()
|
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
|
||||||
|
|
||||||
if pull_output.status.success() {
|
|
||||||
Ok(format!("Successfully force-updated repository at {}", actual_path))
|
|
||||||
} else {
|
|
||||||
let error = String::from_utf8_lossy(&pull_output.stderr);
|
|
||||||
Err(GitError::GitCommandFailed(format!("Git pull error: {}", error)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Commits changes in the repository.
|
||||||
* Commits changes in a git repository and then updates it by pulling the latest changes.
|
///
|
||||||
*
|
/// # Arguments
|
||||||
* # Arguments
|
///
|
||||||
*
|
/// * `message` - The commit message
|
||||||
* * `repo_path` - The path to the git repository, or a partial path that uniquely identifies a repository
|
///
|
||||||
* * `message` - The commit message
|
/// # Returns
|
||||||
*
|
///
|
||||||
* # Returns
|
/// * `Ok(Self)` - The GitRepo object for method chaining
|
||||||
*
|
/// * `Err(GitError)` - If the commit operation failed
|
||||||
* * `Ok(String)` - A success message indicating the repository was committed and updated
|
pub fn commit(&self, message: &str) -> Result<Self, GitError> {
|
||||||
* * `Err(GitError)` - An error if the operation failed
|
|
||||||
*
|
|
||||||
* # Examples
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* let result = git_update_commit("my-project", "Fix bug in login form")?;
|
|
||||||
* println!("{}", result); // "Successfully committed and updated repository at /home/user/code/github.com/user/my-project"
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
pub fn git_update_commit(repo_path: &str, message: &str) -> Result<String, GitError> {
|
|
||||||
// If repo_path may be a partial path, find the matching repository
|
|
||||||
let repos = find_matching_repos(repo_path)?;
|
|
||||||
|
|
||||||
// Should only be one repository at this point
|
|
||||||
let actual_path = &repos[0];
|
|
||||||
|
|
||||||
// Check if repository exists and is a git repository
|
// Check if repository exists and is a git repository
|
||||||
let git_dir = Path::new(actual_path).join(".git");
|
let git_dir = Path::new(&self.path).join(".git");
|
||||||
if !git_dir.exists() || !git_dir.is_dir() {
|
if !git_dir.exists() || !git_dir.is_dir() {
|
||||||
return Err(GitError::NotAGitRepository(actual_path.clone()));
|
return Err(GitError::NotAGitRepository(self.path.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for local changes
|
// Check for local changes
|
||||||
if !has_git_changes(actual_path)? {
|
if !self.has_changes()? {
|
||||||
return Ok(format!("No changes to commit in repository at {}", actual_path));
|
return Ok(self.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all changes
|
// Add all changes
|
||||||
let add_output = Command::new("git")
|
let add_output = Command::new("git")
|
||||||
.args(&["-C", actual_path, "add", "."])
|
.args(&["-C", &self.path, "add", "."])
|
||||||
.output()
|
.output()
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
.map_err(GitError::CommandExecutionError)?;
|
||||||
|
|
||||||
@ -499,7 +439,7 @@ pub fn git_update_commit(repo_path: &str, message: &str) -> Result<String, GitEr
|
|||||||
|
|
||||||
// Commit the changes
|
// Commit the changes
|
||||||
let commit_output = Command::new("git")
|
let commit_output = Command::new("git")
|
||||||
.args(&["-C", actual_path, "commit", "-m", message])
|
.args(&["-C", &self.path, "commit", "-m", message])
|
||||||
.output()
|
.output()
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
.map_err(GitError::CommandExecutionError)?;
|
||||||
|
|
||||||
@ -508,90 +448,42 @@ pub fn git_update_commit(repo_path: &str, message: &str) -> Result<String, GitEr
|
|||||||
return Err(GitError::GitCommandFailed(format!("Git commit error: {}", error)));
|
return Err(GitError::GitCommandFailed(format!("Git commit error: {}", error)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull the latest changes
|
Ok(self.clone())
|
||||||
let pull_output = Command::new("git")
|
|
||||||
.args(&["-C", actual_path, "pull"])
|
|
||||||
.output()
|
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
|
||||||
|
|
||||||
if pull_output.status.success() {
|
|
||||||
Ok(format!("Successfully committed and updated repository at {}", actual_path))
|
|
||||||
} else {
|
|
||||||
let error = String::from_utf8_lossy(&pull_output.stderr);
|
|
||||||
Err(GitError::GitCommandFailed(format!("Git pull error: {}", error)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Pushes changes to the remote repository.
|
||||||
* Commits changes in a git repository and pushes them to the remote.
|
///
|
||||||
*
|
/// # Returns
|
||||||
* # Arguments
|
///
|
||||||
*
|
/// * `Ok(Self)` - The GitRepo object for method chaining
|
||||||
* * `repo_path` - The path to the git repository, or a partial path that uniquely identifies a repository
|
/// * `Err(GitError)` - If the push operation failed
|
||||||
* * `message` - The commit message
|
pub fn push(&self) -> Result<Self, GitError> {
|
||||||
*
|
|
||||||
* # Returns
|
|
||||||
*
|
|
||||||
* * `Ok(String)` - A success message indicating the repository was committed and pushed
|
|
||||||
* * `Err(GitError)` - An error if the operation failed
|
|
||||||
*
|
|
||||||
* # Examples
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* let result = git_update_commit_push("my-project", "Add new feature")?;
|
|
||||||
* println!("{}", result); // "Successfully committed and pushed repository at /home/user/code/github.com/user/my-project"
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
pub fn git_update_commit_push(repo_path: &str, message: &str) -> Result<String, GitError> {
|
|
||||||
// If repo_path may be a partial path, find the matching repository
|
|
||||||
let repos = find_matching_repos(repo_path)?;
|
|
||||||
|
|
||||||
// Should only be one repository at this point
|
|
||||||
let actual_path = &repos[0];
|
|
||||||
|
|
||||||
// Check if repository exists and is a git repository
|
// Check if repository exists and is a git repository
|
||||||
let git_dir = Path::new(actual_path).join(".git");
|
let git_dir = Path::new(&self.path).join(".git");
|
||||||
if !git_dir.exists() || !git_dir.is_dir() {
|
if !git_dir.exists() || !git_dir.is_dir() {
|
||||||
return Err(GitError::NotAGitRepository(actual_path.clone()));
|
return Err(GitError::NotAGitRepository(self.path.clone()));
|
||||||
}
|
|
||||||
|
|
||||||
// Check for local changes
|
|
||||||
if !has_git_changes(actual_path)? {
|
|
||||||
return Ok(format!("No changes to commit in repository at {}", actual_path));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all changes
|
|
||||||
let add_output = Command::new("git")
|
|
||||||
.args(&["-C", actual_path, "add", "."])
|
|
||||||
.output()
|
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
|
||||||
|
|
||||||
if !add_output.status.success() {
|
|
||||||
let error = String::from_utf8_lossy(&add_output.stderr);
|
|
||||||
return Err(GitError::GitCommandFailed(format!("Git add error: {}", error)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit the changes
|
|
||||||
let commit_output = Command::new("git")
|
|
||||||
.args(&["-C", actual_path, "commit", "-m", message])
|
|
||||||
.output()
|
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
|
||||||
|
|
||||||
if !commit_output.status.success() {
|
|
||||||
let error = String::from_utf8_lossy(&commit_output.stderr);
|
|
||||||
return Err(GitError::GitCommandFailed(format!("Git commit error: {}", error)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the changes
|
// Push the changes
|
||||||
let push_output = Command::new("git")
|
let push_output = Command::new("git")
|
||||||
.args(&["-C", actual_path, "push"])
|
.args(&["-C", &self.path, "push"])
|
||||||
.output()
|
.output()
|
||||||
.map_err(GitError::CommandExecutionError)?;
|
.map_err(GitError::CommandExecutionError)?;
|
||||||
|
|
||||||
if push_output.status.success() {
|
if push_output.status.success() {
|
||||||
Ok(format!("Successfully committed and pushed repository at {}", actual_path))
|
Ok(self.clone())
|
||||||
} else {
|
} else {
|
||||||
let error = String::from_utf8_lossy(&push_output.stderr);
|
let error = String::from_utf8_lossy(&push_output.stderr);
|
||||||
Err(GitError::GitCommandFailed(format!("Git push error: {}", error)))
|
Err(GitError::GitCommandFailed(format!("Git push error: {}", error)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement Clone for GitRepo to allow for method chaining
|
||||||
|
impl Clone for GitRepo {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
GitRepo {
|
||||||
|
path: self.path.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -160,7 +160,7 @@ impl GitExecutor {
|
|||||||
// Get authentication configuration for a git URL
|
// Get authentication configuration for a git URL
|
||||||
fn get_auth_for_url(&self, url: &str) -> Option<&GitServerAuth> {
|
fn get_auth_for_url(&self, url: &str) -> Option<&GitServerAuth> {
|
||||||
if let Some(config) = &self.config {
|
if let Some(config) = &self.config {
|
||||||
let (server, _, _) = parse_git_url(url);
|
let (server, _, _) = crate::git::git::parse_git_url(url);
|
||||||
if !server.is_empty() {
|
if !server.is_empty() {
|
||||||
return config.auth.get(&server);
|
return config.auth.get(&server);
|
||||||
}
|
}
|
||||||
|
212
src/git_interface_redesign_plan.md
Normal file
212
src/git_interface_redesign_plan.md
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
# Git Interface Redesign Plan
|
||||||
|
|
||||||
|
## Current Understanding
|
||||||
|
|
||||||
|
The current git interface consists of standalone functions like `git_clone`, `git_list`, `git_update`, etc. We want to replace this with an object-oriented interface using a builder pattern that allows for method chaining.
|
||||||
|
|
||||||
|
## New Interface Design
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class GitTree {
|
||||||
|
+String base_path
|
||||||
|
+new(base_path: &str) Result<GitTree, GitError>
|
||||||
|
+list() Result<Vec<String>, GitError>
|
||||||
|
+find(pattern: &str) Result<Vec<String>, GitError>
|
||||||
|
+get(path_pattern: &str) Result<Vec<GitRepo>, GitError>
|
||||||
|
}
|
||||||
|
|
||||||
|
class GitRepo {
|
||||||
|
+String path
|
||||||
|
+pull() Result<GitRepo, GitError>
|
||||||
|
+reset() Result<GitRepo, GitError>
|
||||||
|
+push() Result<GitRepo, GitError>
|
||||||
|
+commit(message: &str) Result<GitRepo, GitError>
|
||||||
|
+has_changes() Result<bool, GitError>
|
||||||
|
}
|
||||||
|
|
||||||
|
GitTree --> GitRepo : creates
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation Details
|
||||||
|
|
||||||
|
1. **GitTree Class**:
|
||||||
|
- Constructor takes a base path parameter that specifies where all git repositories will be located
|
||||||
|
- Methods for listing and finding repositories
|
||||||
|
- A `get()` method that returns one or more GitRepo objects based on a path pattern
|
||||||
|
- The `get()` method can also accept a URL (git or http format) and will clone the repository if it doesn't exist
|
||||||
|
|
||||||
|
2. **GitRepo Class**:
|
||||||
|
- Represents a single git repository
|
||||||
|
- Methods for common git operations: pull, reset, push, commit
|
||||||
|
- Each method returns a Result containing either the GitRepo object (for chaining) or an error
|
||||||
|
- If an operation fails, subsequent operations in the chain are skipped
|
||||||
|
|
||||||
|
3. **Error Handling**:
|
||||||
|
- Each method returns a Result type for immediate error handling
|
||||||
|
- Errors are propagated up the call chain
|
||||||
|
- The existing GitError enum will be reused
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### 1. Create the GitTree and GitRepo Structs in git.rs
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct GitTree {
|
||||||
|
base_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GitRepo {
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Implement the GitTree Methods
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl GitTree {
|
||||||
|
pub fn new(base_path: &str) -> Result<Self, GitError> {
|
||||||
|
// Validate the base path
|
||||||
|
// Create the directory if it doesn't exist
|
||||||
|
Ok(GitTree {
|
||||||
|
base_path: base_path.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list(&self) -> Result<Vec<String>, GitError> {
|
||||||
|
// List all git repositories under the base path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find(&self, pattern: &str) -> Result<Vec<String>, GitError> {
|
||||||
|
// Find repositories matching the pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, path_pattern: &str) -> Result<Vec<GitRepo>, GitError> {
|
||||||
|
// Find repositories matching the pattern
|
||||||
|
// Return GitRepo objects for each match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Implement the GitRepo Methods
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl GitRepo {
|
||||||
|
pub fn pull(&self) -> Result<Self, GitError> {
|
||||||
|
// Pull the latest changes
|
||||||
|
// Return self for chaining or an error
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&self) -> Result<Self, GitError> {
|
||||||
|
// Reset any local changes
|
||||||
|
// Return self for chaining or an error
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&self) -> Result<Self, GitError> {
|
||||||
|
// Push changes to the remote
|
||||||
|
// Return self for chaining or an error
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit(&self, message: &str) -> Result<Self, GitError> {
|
||||||
|
// Commit changes with the given message
|
||||||
|
// Return self for chaining or an error
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_changes(&self) -> Result<bool, GitError> {
|
||||||
|
// Check if the repository has uncommitted changes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Update the Rhai Wrappers in rhai/git.rs
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Register the GitTree and GitRepo types with Rhai
|
||||||
|
pub fn register_git_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||||
|
// Register the GitTree type
|
||||||
|
engine.register_type::<GitTree>();
|
||||||
|
engine.register_fn("new", git_tree_new);
|
||||||
|
|
||||||
|
// Register GitTree methods
|
||||||
|
engine.register_fn("list", git_tree_list);
|
||||||
|
engine.register_fn("find", git_tree_find);
|
||||||
|
engine.register_fn("get", git_tree_get);
|
||||||
|
|
||||||
|
// Register GitRepo methods
|
||||||
|
engine.register_type::<GitRepo>();
|
||||||
|
engine.register_fn("pull", git_repo_pull);
|
||||||
|
engine.register_fn("reset", git_repo_reset);
|
||||||
|
engine.register_fn("push", git_repo_push);
|
||||||
|
engine.register_fn("commit", git_repo_commit);
|
||||||
|
engine.register_fn("has_changes", git_repo_has_changes);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Update Tests and Examples
|
||||||
|
|
||||||
|
- Update the test files to use the new interface
|
||||||
|
- Create new examples demonstrating the builder pattern and method chaining
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Example 1: Basic Repository Operations
|
||||||
|
|
||||||
|
```rhai
|
||||||
|
// Create a new GitTree object
|
||||||
|
let git_tree = new("/home/user/code");
|
||||||
|
|
||||||
|
// List all repositories
|
||||||
|
let repos = git_tree.list();
|
||||||
|
print(`Found ${repos.len()} repositories`);
|
||||||
|
|
||||||
|
// Find repositories matching a pattern
|
||||||
|
let matching = git_tree.find("my-project*");
|
||||||
|
print(`Found ${matching.len()} matching repositories`);
|
||||||
|
|
||||||
|
// Get a repository and perform operations
|
||||||
|
let repo = git_tree.get("my-project")[0];
|
||||||
|
let result = repo.pull().reset().commit("Update files").push();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: Working with Multiple Repositories
|
||||||
|
|
||||||
|
```rhai
|
||||||
|
// Create a new GitTree object
|
||||||
|
let git_tree = new("/home/user/code");
|
||||||
|
|
||||||
|
// Get all repositories matching a pattern
|
||||||
|
let repos = git_tree.get("project*");
|
||||||
|
print(`Found ${repos.len()} matching repositories`);
|
||||||
|
|
||||||
|
// Perform operations on all repositories
|
||||||
|
for repo in repos {
|
||||||
|
let result = repo.pull();
|
||||||
|
if result.is_ok() {
|
||||||
|
print(`Successfully pulled ${repo.path}`);
|
||||||
|
} else {
|
||||||
|
print(`Failed to pull ${repo.path}: ${result.error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 3: Cloning a Repository
|
||||||
|
|
||||||
|
```rhai
|
||||||
|
// Create a new GitTree object
|
||||||
|
let git_tree = new("/home/user/code");
|
||||||
|
|
||||||
|
// Clone a repository by URL
|
||||||
|
let repos = git_tree.get("https://github.com/username/repo.git");
|
||||||
|
let repo = repos[0];
|
||||||
|
print(`Repository cloned to: ${repo.path}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Strategy
|
||||||
|
|
||||||
|
1. Implement the new interface in git.rs and rhai/git.rs
|
||||||
|
2. Update all tests and examples to use the new interface
|
||||||
|
3. Remove the old standalone functions
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, Array, Dynamic, Map};
|
use rhai::{Engine, EvalAltResult, Array, Dynamic, Map};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::virt::buildah::{self, BuildahError, Image};
|
use crate::virt::buildah::{self, BuildahError, Image, Builder};
|
||||||
use crate::process::CommandResult;
|
use crate::process::CommandResult;
|
||||||
|
|
||||||
/// Register Buildah module functions with the Rhai engine
|
/// Register Buildah module functions with the Rhai engine
|
||||||
@ -20,35 +20,42 @@ pub fn register_bah_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>
|
|||||||
// Register types
|
// Register types
|
||||||
register_bah_types(engine)?;
|
register_bah_types(engine)?;
|
||||||
|
|
||||||
// Register container functions
|
// Register Builder constructor
|
||||||
engine.register_fn("bah_from", bah_from);
|
engine.register_fn("bah_new", bah_new);
|
||||||
engine.register_fn("bah_run", bah_run);
|
|
||||||
engine.register_fn("bah_run_with_isolation", bah_run_with_isolation);
|
|
||||||
engine.register_fn("bah_copy", bah_copy);
|
|
||||||
engine.register_fn("bah_add", bah_add);
|
|
||||||
engine.register_fn("bah_commit", bah_commit);
|
|
||||||
engine.register_fn("bah_remove", bah_remove);
|
|
||||||
engine.register_fn("bah_list", bah_list);
|
|
||||||
engine.register_fn("bah_build", bah_build_with_options);
|
|
||||||
engine.register_fn("bah_new_build_options", new_build_options);
|
|
||||||
|
|
||||||
// Register image functions
|
// Register Builder instance methods
|
||||||
engine.register_fn("bah_images", images);
|
engine.register_fn("run", builder_run);
|
||||||
engine.register_fn("bah_image_remove", image_remove);
|
engine.register_fn("run_with_isolation", builder_run_with_isolation);
|
||||||
engine.register_fn("bah_image_push", image_push);
|
engine.register_fn("copy", builder_copy);
|
||||||
engine.register_fn("bah_image_tag", image_tag);
|
engine.register_fn("add", builder_add);
|
||||||
engine.register_fn("bah_image_pull", image_pull);
|
engine.register_fn("commit", builder_commit);
|
||||||
engine.register_fn("bah_image_commit", image_commit_with_options);
|
// Remove the line that's causing the error
|
||||||
engine.register_fn("bah_new_commit_options", new_commit_options);
|
engine.register_fn("remove", builder_remove);
|
||||||
engine.register_fn("bah_config", config_with_options);
|
engine.register_fn("reset", builder_reset);
|
||||||
engine.register_fn("bah_new_config_options", new_config_options);
|
engine.register_fn("config", builder_config);
|
||||||
|
|
||||||
|
// Register Builder static methods
|
||||||
|
engine.register_fn("images", builder_images);
|
||||||
|
engine.register_fn("image_remove", builder_image_remove);
|
||||||
|
engine.register_fn("image_pull", builder_image_pull);
|
||||||
|
engine.register_fn("image_push", builder_image_push);
|
||||||
|
engine.register_fn("image_tag", builder_image_tag);
|
||||||
|
engine.register_fn("build", builder_build);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register Buildah module types with the Rhai engine
|
/// Register Buildah module types with the Rhai engine
|
||||||
fn register_bah_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
fn register_bah_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||||
// Register Image type and methods
|
// Register Builder type
|
||||||
|
engine.register_type_with_name::<Builder>("BuildahBuilder");
|
||||||
|
|
||||||
|
// Register getters for Builder properties
|
||||||
|
engine.register_get("container_id", get_builder_container_id);
|
||||||
|
engine.register_get("name", get_builder_name);
|
||||||
|
engine.register_get("image", get_builder_image);
|
||||||
|
|
||||||
|
// Register Image type and methods (same as before)
|
||||||
engine.register_type_with_name::<Image>("BuildahImage");
|
engine.register_type_with_name::<Image>("BuildahImage");
|
||||||
|
|
||||||
// Register getters for Image properties
|
// Register getters for Image properties
|
||||||
@ -84,312 +91,8 @@ fn bah_error_to_rhai_error<T>(result: Result<T, BuildahError>) -> Result<T, Box<
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new Map with default build options
|
// Helper function to convert Rhai Map to Rust HashMap
|
||||||
pub fn new_build_options() -> Map {
|
fn convert_map_to_hashmap(options: Map) -> Result<HashMap<String, String>, Box<EvalAltResult>> {
|
||||||
let mut map = Map::new();
|
|
||||||
map.insert("tag".into(), Dynamic::UNIT);
|
|
||||||
map.insert("context_dir".into(), Dynamic::from("."));
|
|
||||||
map.insert("file".into(), Dynamic::from("Dockerfile"));
|
|
||||||
map.insert("isolation".into(), Dynamic::UNIT);
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new Map with default commit options
|
|
||||||
pub fn new_commit_options() -> Map {
|
|
||||||
let mut map = Map::new();
|
|
||||||
map.insert("format".into(), Dynamic::UNIT);
|
|
||||||
map.insert("squash".into(), Dynamic::from(false));
|
|
||||||
map.insert("rm".into(), Dynamic::from(false));
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new Map for config options
|
|
||||||
pub fn new_config_options() -> Map {
|
|
||||||
Map::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Container Function Wrappers
|
|
||||||
//
|
|
||||||
|
|
||||||
/// Wrapper for buildah::from
|
|
||||||
///
|
|
||||||
/// Create a container from an image.
|
|
||||||
pub fn bah_from(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
let result = bah_error_to_rhai_error(buildah::from(image))?;
|
|
||||||
|
|
||||||
// Create a new CommandResult with trimmed stdout
|
|
||||||
let trimmed_result = CommandResult {
|
|
||||||
stdout: result.stdout.trim().to_string(),
|
|
||||||
stderr: result.stderr.trim().to_string(),
|
|
||||||
success: result.success,
|
|
||||||
code: result.code,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(trimmed_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for buildah::run
|
|
||||||
///
|
|
||||||
/// Run a command in a container.
|
|
||||||
pub fn bah_run(container: &str, command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
bah_error_to_rhai_error(buildah::run(container, command))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for buildah::run_with_isolation
|
|
||||||
///
|
|
||||||
/// Run a command in a container with specified isolation.
|
|
||||||
pub fn bah_run_with_isolation(container: &str, command: &str, isolation: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
bah_error_to_rhai_error(buildah::bah_run_with_isolation(container, command, isolation))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for buildah::copy
|
|
||||||
///
|
|
||||||
/// Copy files into a container.
|
|
||||||
pub fn bah_copy(container: &str, source: &str, dest: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
bah_error_to_rhai_error(buildah::bah_copy(container, source, dest))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for buildah::add
|
|
||||||
///
|
|
||||||
/// Add files into a container.
|
|
||||||
pub fn bah_add(container: &str, source: &str, dest: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
bah_error_to_rhai_error(buildah::bah_add(container, source, dest))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for buildah::commit
|
|
||||||
///
|
|
||||||
/// Commit a container to an image.
|
|
||||||
pub fn bah_commit(container: &str, image_name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
bah_error_to_rhai_error(buildah::bah_commit(container, image_name))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for buildah::remove
|
|
||||||
///
|
|
||||||
/// Remove a container.
|
|
||||||
pub fn bah_remove(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
bah_error_to_rhai_error(buildah::bah_remove(container))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for buildah::list
|
|
||||||
///
|
|
||||||
/// List containers.
|
|
||||||
pub fn bah_list() -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
bah_error_to_rhai_error(buildah::bah_list())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build an image with options specified in a Map
|
|
||||||
///
|
|
||||||
/// This provides a builder-style interface for Rhai scripts.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let options = bah_new_build_options();
|
|
||||||
/// options.tag = "my-image:latest";
|
|
||||||
/// options.context_dir = ".";
|
|
||||||
/// options.file = "Dockerfile";
|
|
||||||
/// options.isolation = "chroot";
|
|
||||||
/// let result = bah_build(options);
|
|
||||||
/// ```
|
|
||||||
pub fn bah_build_with_options(options: Map) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
// Extract options from the map
|
|
||||||
let tag_option = match options.get("tag") {
|
|
||||||
Some(tag) => {
|
|
||||||
if tag.is_unit() {
|
|
||||||
None
|
|
||||||
} else if let Ok(tag_str) = tag.clone().into_string() {
|
|
||||||
Some(tag_str)
|
|
||||||
} else {
|
|
||||||
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
"tag must be a string".into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => None
|
|
||||||
};
|
|
||||||
|
|
||||||
let context_dir = match options.get("context_dir") {
|
|
||||||
Some(dir) => {
|
|
||||||
if let Ok(dir_str) = dir.clone().into_string() {
|
|
||||||
dir_str
|
|
||||||
} else {
|
|
||||||
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
"context_dir must be a string".into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => String::from(".")
|
|
||||||
};
|
|
||||||
|
|
||||||
let file = match options.get("file") {
|
|
||||||
Some(file) => {
|
|
||||||
if let Ok(file_str) = file.clone().into_string() {
|
|
||||||
file_str
|
|
||||||
} else {
|
|
||||||
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
"file must be a string".into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => String::from("Dockerfile")
|
|
||||||
};
|
|
||||||
|
|
||||||
let isolation_option = match options.get("isolation") {
|
|
||||||
Some(isolation) => {
|
|
||||||
if isolation.is_unit() {
|
|
||||||
None
|
|
||||||
} else if let Ok(isolation_str) = isolation.clone().into_string() {
|
|
||||||
Some(isolation_str)
|
|
||||||
} else {
|
|
||||||
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
"isolation must be a string".into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => None
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert String to &str for the function call
|
|
||||||
let tag_ref = tag_option.as_deref();
|
|
||||||
let isolation_ref = isolation_option.as_deref();
|
|
||||||
|
|
||||||
// Call the buildah build function
|
|
||||||
bah_error_to_rhai_error(buildah::bah_build(tag_ref, &context_dir, &file, isolation_ref))
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Image Function Wrappers
|
|
||||||
//
|
|
||||||
|
|
||||||
/// Wrapper for buildah::images
|
|
||||||
///
|
|
||||||
/// List images in local storage.
|
|
||||||
pub fn images() -> Result<Array, Box<EvalAltResult>> {
|
|
||||||
let images = bah_error_to_rhai_error(buildah::images())?;
|
|
||||||
|
|
||||||
// Convert Vec<Image> to Rhai Array
|
|
||||||
let mut array = Array::new();
|
|
||||||
for image in images {
|
|
||||||
array.push(Dynamic::from(image));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(array)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for buildah::image_remove
|
|
||||||
///
|
|
||||||
/// Remove one or more images.
|
|
||||||
pub fn image_remove(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
bah_error_to_rhai_error(buildah::image_remove(image))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for buildah::image_push
|
|
||||||
///
|
|
||||||
/// Push an image to a registry.
|
|
||||||
pub fn image_push(image: &str, destination: &str, tls_verify: bool) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
bah_error_to_rhai_error(buildah::image_push(image, destination, tls_verify))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for buildah::image_tag
|
|
||||||
///
|
|
||||||
/// Add an additional name to a local image.
|
|
||||||
pub fn image_tag(image: &str, new_name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
bah_error_to_rhai_error(buildah::image_tag(image, new_name))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for buildah::image_pull
|
|
||||||
///
|
|
||||||
/// Pull an image from a registry.
|
|
||||||
pub fn image_pull(image: &str, tls_verify: bool) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
bah_error_to_rhai_error(buildah::image_pull(image, tls_verify))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Commit a container to an image with options specified in a Map
|
|
||||||
///
|
|
||||||
/// This provides a builder-style interface for Rhai scripts.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let options = bah_new_commit_options();
|
|
||||||
/// options.format = "docker";
|
|
||||||
/// options.squash = true;
|
|
||||||
/// options.rm = true;
|
|
||||||
/// let result = bah_image_commit("my-container", "my-image:latest", options);
|
|
||||||
/// ```
|
|
||||||
pub fn image_commit_with_options(container: &str, image_name: &str, options: Map) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
// Extract options from the map
|
|
||||||
let format_option = match options.get("format") {
|
|
||||||
Some(format) => {
|
|
||||||
if format.is_unit() {
|
|
||||||
None
|
|
||||||
} else if let Ok(format_str) = format.clone().into_string() {
|
|
||||||
Some(format_str)
|
|
||||||
} else {
|
|
||||||
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
"format must be a string".into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => None
|
|
||||||
};
|
|
||||||
|
|
||||||
let squash = match options.get("squash") {
|
|
||||||
Some(squash) => {
|
|
||||||
if let Ok(squash_val) = squash.clone().as_bool() {
|
|
||||||
squash_val
|
|
||||||
} else {
|
|
||||||
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
"squash must be a boolean".into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => false
|
|
||||||
};
|
|
||||||
|
|
||||||
let rm = match options.get("rm") {
|
|
||||||
Some(rm) => {
|
|
||||||
if let Ok(rm_val) = rm.clone().as_bool() {
|
|
||||||
rm_val
|
|
||||||
} else {
|
|
||||||
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
"rm must be a boolean".into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => false
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert String to &str for the function call
|
|
||||||
let format_ref = format_option.as_deref();
|
|
||||||
|
|
||||||
// Call the buildah image_commit function
|
|
||||||
bah_error_to_rhai_error(buildah::image_commit(container, image_name, format_ref, squash, rm))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure a container with options specified in a Map
|
|
||||||
///
|
|
||||||
/// This provides a builder-style interface for Rhai scripts.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let options = bah_new_config_options();
|
|
||||||
/// options.author = "John Doe";
|
|
||||||
/// options.cmd = "echo Hello";
|
|
||||||
/// options.entrypoint = "/bin/sh -c";
|
|
||||||
/// let result = bah_config("my-container", options);
|
|
||||||
/// ```
|
|
||||||
pub fn config_with_options(container: &str, options: Map) -> Result<CommandResult, Box<EvalAltResult>> {
|
|
||||||
// Convert Rhai Map to Rust HashMap
|
|
||||||
let mut config_options = HashMap::<String, String>::new();
|
let mut config_options = HashMap::<String, String>::new();
|
||||||
|
|
||||||
for (key, value) in options.iter() {
|
for (key, value) in options.iter() {
|
||||||
@ -404,6 +107,96 @@ pub fn config_with_options(container: &str, options: Map) -> Result<CommandResul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the buildah config function
|
Ok(config_options)
|
||||||
bah_error_to_rhai_error(buildah::bah_config(container, config_options))
|
}
|
||||||
|
|
||||||
|
/// Create a new Builder
|
||||||
|
pub fn bah_new(name: &str, image: &str) -> Result<Builder, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(Builder::new(name, image))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder instance methods
|
||||||
|
pub fn builder_run(builder: &mut Builder, command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.run(command))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder_run_with_isolation(builder: &mut Builder, command: &str, isolation: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.run_with_isolation(command, isolation))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder_copy(builder: &mut Builder, source: &str, dest: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.copy(source, dest))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder_add(builder: &mut Builder, source: &str, dest: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.add(source, dest))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder_commit(builder: &mut Builder, image_name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.commit(image_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder_remove(builder: &mut Builder) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.remove())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder_config(builder: &mut Builder, options: Map) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
// Convert Rhai Map to Rust HashMap
|
||||||
|
let config_options = convert_map_to_hashmap(options)?;
|
||||||
|
bah_error_to_rhai_error(builder.config(config_options))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder static methods
|
||||||
|
pub fn builder_images(_builder: &mut Builder) -> Result<Array, Box<EvalAltResult>> {
|
||||||
|
let images = bah_error_to_rhai_error(Builder::images())?;
|
||||||
|
|
||||||
|
// Convert Vec<Image> to Rhai Array
|
||||||
|
let mut array = Array::new();
|
||||||
|
for image in images {
|
||||||
|
array.push(Dynamic::from(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder_image_remove(_builder: &mut Builder, image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(Builder::image_remove(image))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder_image_pull(_builder: &mut Builder, image: &str, tls_verify: bool) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(Builder::image_pull(image, tls_verify))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder_image_push(_builder: &mut Builder, image: &str, destination: &str, tls_verify: bool) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(Builder::image_push(image, destination, tls_verify))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder_image_tag(_builder: &mut Builder, image: &str, new_name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(Builder::image_tag(image, new_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter functions for Builder properties
|
||||||
|
pub fn get_builder_container_id(builder: &mut Builder) -> String {
|
||||||
|
match builder.container_id() {
|
||||||
|
Some(id) => id.clone(),
|
||||||
|
None => "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_builder_name(builder: &mut Builder) -> String {
|
||||||
|
builder.name().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_builder_image(builder: &mut Builder) -> String {
|
||||||
|
builder.image().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset function for Builder
|
||||||
|
pub fn builder_reset(builder: &mut Builder) -> Result<(), Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(builder.reset())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build function for Builder
|
||||||
|
pub fn builder_build(_builder: &mut Builder, tag: &str, context_dir: &str, file: &str, isolation: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||||
|
bah_error_to_rhai_error(Builder::build(Some(tag), context_dir, file, Some(isolation)))
|
||||||
}
|
}
|
124
src/rhai/git.rs
124
src/rhai/git.rs
@ -3,7 +3,7 @@
|
|||||||
//! This module provides Rhai wrappers for the functions in the Git module.
|
//! This module provides Rhai wrappers for the functions in the Git module.
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, Array, Dynamic};
|
use rhai::{Engine, EvalAltResult, Array, Dynamic};
|
||||||
use crate::git::{self, GitError};
|
use crate::git::{GitTree, GitRepo, GitError};
|
||||||
|
|
||||||
/// Register Git module functions with the Rhai engine
|
/// Register Git module functions with the Rhai engine
|
||||||
///
|
///
|
||||||
@ -15,15 +15,21 @@ use crate::git::{self, GitError};
|
|||||||
///
|
///
|
||||||
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
|
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
|
||||||
pub fn register_git_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
pub fn register_git_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||||
// Register basic git functions
|
// Register GitTree constructor
|
||||||
engine.register_fn("git_clone", git_clone);
|
engine.register_fn("gittree_new", git_tree_new);
|
||||||
engine.register_fn("git_list", git_list);
|
|
||||||
engine.register_fn("git_update", git_update);
|
// Register GitTree methods
|
||||||
engine.register_fn("git_update_force", git_update_force);
|
engine.register_fn("list", git_tree_list);
|
||||||
engine.register_fn("git_update_commit", git_update_commit);
|
engine.register_fn("find", git_tree_find);
|
||||||
engine.register_fn("git_update_commit_push", git_update_commit_push);
|
engine.register_fn("get", git_tree_get);
|
||||||
engine.register_fn("git_has_changes", has_git_changes);
|
|
||||||
engine.register_fn("git_find_repos", find_matching_repos);
|
// Register GitRepo methods
|
||||||
|
engine.register_fn("path", git_repo_path);
|
||||||
|
engine.register_fn("has_changes", git_repo_has_changes);
|
||||||
|
engine.register_fn("pull", git_repo_pull);
|
||||||
|
engine.register_fn("reset", git_repo_reset);
|
||||||
|
engine.register_fn("commit", git_repo_commit);
|
||||||
|
engine.register_fn("push", git_repo_push);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -39,21 +45,21 @@ fn git_error_to_rhai_error<T>(result: Result<T, GitError>) -> Result<T, Box<Eval
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Git Function Wrappers
|
// GitTree Function Wrappers
|
||||||
//
|
//
|
||||||
|
|
||||||
/// Wrapper for git::git_clone
|
/// Wrapper for GitTree::new
|
||||||
///
|
///
|
||||||
/// Clones a git repository to a standardized location in the user's home directory.
|
/// Creates a new GitTree with the specified base path.
|
||||||
pub fn git_clone(url: &str) -> Result<String, Box<EvalAltResult>> {
|
pub fn git_tree_new(base_path: &str) -> Result<GitTree, Box<EvalAltResult>> {
|
||||||
git_error_to_rhai_error(git::git_clone(url))
|
git_error_to_rhai_error(GitTree::new(base_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for git::git_list
|
/// Wrapper for GitTree::list
|
||||||
///
|
///
|
||||||
/// Lists all git repositories found in the user's ~/code directory.
|
/// Lists all git repositories under the base path.
|
||||||
pub fn git_list() -> Result<Array, Box<EvalAltResult>> {
|
pub fn git_tree_list(git_tree: &mut GitTree) -> Result<Array, Box<EvalAltResult>> {
|
||||||
let repos = git_error_to_rhai_error(git::git_list())?;
|
let repos = git_error_to_rhai_error(git_tree.list())?;
|
||||||
|
|
||||||
// Convert Vec<String> to Rhai Array
|
// Convert Vec<String> to Rhai Array
|
||||||
let mut array = Array::new();
|
let mut array = Array::new();
|
||||||
@ -64,18 +70,11 @@ pub fn git_list() -> Result<Array, Box<EvalAltResult>> {
|
|||||||
Ok(array)
|
Ok(array)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for git::has_git_changes
|
/// Wrapper for GitTree::find
|
||||||
///
|
|
||||||
/// Checks if a git repository has uncommitted changes.
|
|
||||||
pub fn has_git_changes(repo_path: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
git_error_to_rhai_error(git::has_git_changes(repo_path))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for git::find_matching_repos
|
|
||||||
///
|
///
|
||||||
/// Finds repositories matching a pattern or partial path.
|
/// Finds repositories matching a pattern or partial path.
|
||||||
pub fn find_matching_repos(pattern: &str) -> Result<Array, Box<EvalAltResult>> {
|
pub fn git_tree_find(git_tree: &mut GitTree, pattern: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||||
let repos = git_error_to_rhai_error(git::find_matching_repos(pattern))?;
|
let repos = git_error_to_rhai_error(git_tree.find(pattern))?;
|
||||||
|
|
||||||
// Convert Vec<String> to Rhai Array
|
// Convert Vec<String> to Rhai Array
|
||||||
let mut array = Array::new();
|
let mut array = Array::new();
|
||||||
@ -86,30 +85,63 @@ pub fn find_matching_repos(pattern: &str) -> Result<Array, Box<EvalAltResult>> {
|
|||||||
Ok(array)
|
Ok(array)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for git::git_update
|
/// Wrapper for GitTree::get
|
||||||
///
|
///
|
||||||
/// Updates a git repository by pulling the latest changes.
|
/// Gets one or more GitRepo objects based on a path pattern or URL.
|
||||||
pub fn git_update(repo_path: &str) -> Result<String, Box<EvalAltResult>> {
|
pub fn git_tree_get(git_tree: &mut GitTree, path_or_url: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||||
git_error_to_rhai_error(git::git_update(repo_path))
|
let repos = git_error_to_rhai_error(git_tree.get(path_or_url))?;
|
||||||
|
|
||||||
|
// Convert Vec<GitRepo> to Rhai Array
|
||||||
|
let mut array = Array::new();
|
||||||
|
for repo in repos {
|
||||||
|
array.push(Dynamic::from(repo));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for git::git_update_force
|
Ok(array)
|
||||||
///
|
|
||||||
/// Force updates a git repository by discarding local changes and pulling the latest changes.
|
|
||||||
pub fn git_update_force(repo_path: &str) -> Result<String, Box<EvalAltResult>> {
|
|
||||||
git_error_to_rhai_error(git::git_update_force(repo_path))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for git::git_update_commit
|
//
|
||||||
|
// GitRepo Function Wrappers
|
||||||
|
//
|
||||||
|
|
||||||
|
/// Wrapper for GitRepo::path
|
||||||
///
|
///
|
||||||
/// Commits changes in a git repository and then updates it by pulling the latest changes.
|
/// Gets the path of the repository.
|
||||||
pub fn git_update_commit(repo_path: &str, message: &str) -> Result<String, Box<EvalAltResult>> {
|
pub fn git_repo_path(git_repo: &mut GitRepo) -> String {
|
||||||
git_error_to_rhai_error(git::git_update_commit(repo_path, message))
|
git_repo.path().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for git::git_update_commit_push
|
/// Wrapper for GitRepo::has_changes
|
||||||
///
|
///
|
||||||
/// Commits changes in a git repository and pushes them to the remote.
|
/// Checks if the repository has uncommitted changes.
|
||||||
pub fn git_update_commit_push(repo_path: &str, message: &str) -> Result<String, Box<EvalAltResult>> {
|
pub fn git_repo_has_changes(git_repo: &mut GitRepo) -> Result<bool, Box<EvalAltResult>> {
|
||||||
git_error_to_rhai_error(git::git_update_commit_push(repo_path, message))
|
git_error_to_rhai_error(git_repo.has_changes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for GitRepo::pull
|
||||||
|
///
|
||||||
|
/// Pulls the latest changes from the remote repository.
|
||||||
|
pub fn git_repo_pull(git_repo: &mut GitRepo) -> Result<GitRepo, Box<EvalAltResult>> {
|
||||||
|
git_error_to_rhai_error(git_repo.pull())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for GitRepo::reset
|
||||||
|
///
|
||||||
|
/// Resets any local changes in the repository.
|
||||||
|
pub fn git_repo_reset(git_repo: &mut GitRepo) -> Result<GitRepo, Box<EvalAltResult>> {
|
||||||
|
git_error_to_rhai_error(git_repo.reset())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for GitRepo::commit
|
||||||
|
///
|
||||||
|
/// Commits changes in the repository.
|
||||||
|
pub fn git_repo_commit(git_repo: &mut GitRepo, message: &str) -> Result<GitRepo, Box<EvalAltResult>> {
|
||||||
|
git_error_to_rhai_error(git_repo.commit(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for GitRepo::push
|
||||||
|
///
|
||||||
|
/// Pushes changes to the remote repository.
|
||||||
|
pub fn git_repo_push(git_repo: &mut GitRepo) -> Result<GitRepo, Box<EvalAltResult>> {
|
||||||
|
git_error_to_rhai_error(git_repo.push())
|
||||||
}
|
}
|
@ -39,25 +39,22 @@ pub use process::{
|
|||||||
|
|
||||||
// Re-export buildah functions
|
// Re-export buildah functions
|
||||||
pub use buildah::register_bah_module;
|
pub use buildah::register_bah_module;
|
||||||
pub use buildah::{
|
pub use buildah::bah_new;
|
||||||
bah_from, bah_run, bah_run_with_isolation, bah_copy, bah_add, bah_commit,
|
|
||||||
bah_remove, bah_list, bah_build_with_options,
|
|
||||||
new_commit_options, new_config_options, image_commit_with_options, config_with_options
|
|
||||||
};
|
|
||||||
|
|
||||||
// Re-export nerdctl functions
|
// Re-export nerdctl functions
|
||||||
pub use nerdctl::register_nerdctl_module;
|
pub use nerdctl::register_nerdctl_module;
|
||||||
pub use nerdctl::{
|
pub use nerdctl::{
|
||||||
nerdctl_run, nerdctl_exec,
|
// Container functions
|
||||||
nerdctl_copy, nerdctl_stop, nerdctl_remove, nerdctl_list
|
nerdctl_run, nerdctl_run_with_name, nerdctl_run_with_port,
|
||||||
|
nerdctl_exec, nerdctl_copy, nerdctl_stop, nerdctl_remove, nerdctl_list,
|
||||||
|
// Image functions
|
||||||
|
nerdctl_images, nerdctl_image_remove, nerdctl_image_push, nerdctl_image_tag,
|
||||||
|
nerdctl_image_pull, nerdctl_image_commit, nerdctl_image_build
|
||||||
};
|
};
|
||||||
|
|
||||||
// Re-export git functions
|
// Re-export git module
|
||||||
pub use git::register_git_module;
|
pub use git::register_git_module;
|
||||||
pub use git::{
|
pub use crate::git::{GitTree, GitRepo};
|
||||||
git_clone, git_list, git_update, git_update_force, git_update_commit,
|
|
||||||
git_update_commit_push, has_git_changes, find_matching_repos
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rename copy functions to avoid conflicts
|
// Rename copy functions to avoid conflicts
|
||||||
pub use os::copy as os_copy;
|
pub use os::copy as os_copy;
|
||||||
|
@ -4,38 +4,28 @@
|
|||||||
import "os" as os;
|
import "os" as os;
|
||||||
import "process" as process;
|
import "process" as process;
|
||||||
|
|
||||||
// Test git_clone function
|
// Test GitTree creation
|
||||||
fn test_git_clone() {
|
fn test_git_tree_creation() {
|
||||||
// Use a public repository for testing
|
// Get home directory
|
||||||
let repo_url = "https://github.com/rhaiscript/rhai.git";
|
let home_dir = env("HOME");
|
||||||
|
let code_dir = `${home_dir}/code`;
|
||||||
|
|
||||||
// Clone the repository
|
print("Testing GitTree creation...");
|
||||||
print("Testing git_clone...");
|
let git_tree = gittree_new(code_dir);
|
||||||
let result = git_clone(repo_url);
|
|
||||||
|
|
||||||
// Print the result
|
print(`Created GitTree with base path: ${code_dir}`);
|
||||||
print(`Clone result: ${result}`);
|
|
||||||
|
|
||||||
// Verify the repository exists
|
|
||||||
if result.contains("already exists") {
|
|
||||||
print("Repository already exists, test passed");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the path exists
|
// Test GitTree list method
|
||||||
if exist(result) {
|
fn test_git_tree_list() {
|
||||||
print("Repository cloned successfully, test passed");
|
// Get home directory
|
||||||
return true;
|
let home_dir = env("HOME");
|
||||||
}
|
let code_dir = `${home_dir}/code`;
|
||||||
|
|
||||||
print("Repository clone failed");
|
print("Testing GitTree list method...");
|
||||||
return false;
|
let git_tree = gittree_new(code_dir);
|
||||||
}
|
let repos = git_tree.list();
|
||||||
|
|
||||||
// Test git_list function
|
|
||||||
fn test_git_list() {
|
|
||||||
print("Testing git_list...");
|
|
||||||
let repos = git_list();
|
|
||||||
|
|
||||||
print(`Found ${repos.len()} repositories`);
|
print(`Found ${repos.len()} repositories`);
|
||||||
|
|
||||||
@ -45,44 +35,32 @@ fn test_git_list() {
|
|||||||
print(` - ${repos[i]}`);
|
print(` - ${repos[i]}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos.len() > 0;
|
return repos.len() >= 0; // Success even if no repos found
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test git_has_changes function
|
// Test GitTree find method
|
||||||
fn test_git_has_changes() {
|
fn test_git_tree_find() {
|
||||||
print("Testing git_has_changes...");
|
// Get home directory
|
||||||
|
let home_dir = env("HOME");
|
||||||
|
let code_dir = `${home_dir}/code`;
|
||||||
|
|
||||||
|
print("Testing GitTree find method...");
|
||||||
|
let git_tree = gittree_new(code_dir);
|
||||||
|
let repos = git_tree.list();
|
||||||
|
|
||||||
// Get a repository from the list
|
|
||||||
let repos = git_list();
|
|
||||||
if repos.len() == 0 {
|
if repos.len() == 0 {
|
||||||
print("No repositories found, skipping test");
|
print("No repositories found, skipping test");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let repo = repos[0];
|
|
||||||
let has_changes = git_has_changes(repo);
|
|
||||||
|
|
||||||
print(`Repository ${repo} has changes: ${has_changes}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test find_matching_repos function
|
|
||||||
fn test_find_matching_repos() {
|
|
||||||
print("Testing find_matching_repos...");
|
|
||||||
|
|
||||||
// Get all repositories with wildcard
|
|
||||||
let all_repos = git_list();
|
|
||||||
if all_repos.len() == 0 {
|
|
||||||
print("No repositories found, skipping test");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract a part of the first repo name to search for
|
// Extract a part of the first repo name to search for
|
||||||
let repo_name = all_repos[0].split("/").last();
|
let repo_path = repos[0];
|
||||||
let search_pattern = repo_name.substring(0, 3) + "*";
|
let parts = repo_path.split("/");
|
||||||
|
let repo_name = parts[parts.len() - 1];
|
||||||
|
let search_pattern = repo_name + "*";
|
||||||
|
|
||||||
print(`Searching for repositories matching pattern: ${search_pattern}`);
|
print(`Searching for repositories matching pattern: ${search_pattern}`);
|
||||||
let matching = find_matching_repos(search_pattern);
|
let matching = git_tree.find(search_pattern);
|
||||||
|
|
||||||
print(`Found ${matching.len()} matching repositories`);
|
print(`Found ${matching.len()} matching repositories`);
|
||||||
for repo in matching {
|
for repo in matching {
|
||||||
@ -92,13 +70,74 @@ fn test_find_matching_repos() {
|
|||||||
return matching.len() > 0;
|
return matching.len() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test GitTree get method
|
||||||
|
fn test_git_tree_get() {
|
||||||
|
// Get home directory
|
||||||
|
let home_dir = env("HOME");
|
||||||
|
let code_dir = `${home_dir}/code`;
|
||||||
|
|
||||||
|
print("Testing GitTree get method...");
|
||||||
|
let git_tree = gittree_new(code_dir);
|
||||||
|
let repos = git_tree.list();
|
||||||
|
|
||||||
|
if repos.len() == 0 {
|
||||||
|
print("No repositories found, skipping test");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract a part of the first repo name to search for
|
||||||
|
let repo_path = repos[0];
|
||||||
|
let parts = repo_path.split("/");
|
||||||
|
let repo_name = parts[parts.len() - 1];
|
||||||
|
|
||||||
|
print(`Getting GitRepo objects for: ${repo_name}`);
|
||||||
|
let git_repos = git_tree.get(repo_name);
|
||||||
|
|
||||||
|
print(`Found ${git_repos.len()} GitRepo objects`);
|
||||||
|
for repo in git_repos {
|
||||||
|
print(` - ${repo.path()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return git_repos.len() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GitRepo has_changes method
|
||||||
|
fn test_git_repo_has_changes() {
|
||||||
|
// Get home directory
|
||||||
|
let home_dir = env("HOME");
|
||||||
|
let code_dir = `${home_dir}/code`;
|
||||||
|
|
||||||
|
print("Testing GitRepo has_changes method...");
|
||||||
|
let git_tree = gittree_new(code_dir);
|
||||||
|
let repos = git_tree.list();
|
||||||
|
|
||||||
|
if repos.len() == 0 {
|
||||||
|
print("No repositories found, skipping test");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first repo
|
||||||
|
let git_repos = git_tree.get(repos[0]);
|
||||||
|
if git_repos.len() == 0 {
|
||||||
|
print("Failed to get GitRepo object, skipping test");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let git_repo = git_repos[0];
|
||||||
|
let has_changes = git_repo.has_changes();
|
||||||
|
|
||||||
|
print(`Repository ${git_repo.path()} has changes: ${has_changes}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Run the tests
|
// Run the tests
|
||||||
fn run_tests() {
|
fn run_tests() {
|
||||||
let tests = [
|
let tests = [
|
||||||
#{ name: "git_clone", fn: test_git_clone },
|
#{ name: "git_tree_creation", fn: test_git_tree_creation },
|
||||||
#{ name: "git_list", fn: test_git_list },
|
#{ name: "git_tree_list", fn: test_git_tree_list },
|
||||||
#{ name: "git_has_changes", fn: test_git_has_changes },
|
#{ name: "git_tree_find", fn: test_git_tree_find },
|
||||||
#{ name: "find_matching_repos", fn: test_find_matching_repos }
|
#{ name: "git_tree_get", fn: test_git_tree_get },
|
||||||
|
#{ name: "git_repo_has_changes", fn: test_git_repo_has_changes }
|
||||||
];
|
];
|
||||||
|
|
||||||
let passed = 0;
|
let passed = 0;
|
||||||
|
11
src/simple_git_test.rhai
Normal file
11
src/simple_git_test.rhai
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Simple test script for Git module functions
|
||||||
|
|
||||||
|
// Print a header
|
||||||
|
print("=== Testing Git Module Functions ===\n");
|
||||||
|
|
||||||
|
// Create a new GitTree object
|
||||||
|
let home_dir = env("HOME");
|
||||||
|
let git_tree = gittree_new(`${home_dir}/code`);
|
||||||
|
print(`Created GitTree with base path: ${home_dir}/code`);
|
||||||
|
|
||||||
|
print("\n=== Git Module Test Complete ===");
|
@ -3,9 +3,14 @@
|
|||||||
// Print a header
|
// Print a header
|
||||||
print("=== Testing Git Module Functions ===\n");
|
print("=== Testing Git Module Functions ===\n");
|
||||||
|
|
||||||
// Test git_list function
|
// Create a new GitTree object
|
||||||
print("Listing git repositories...");
|
let home_dir = env("HOME");
|
||||||
let repos = git_list();
|
let git_tree = gittree_new(`${home_dir}/code`);
|
||||||
|
print(`Created GitTree with base path: ${home_dir}/code`);
|
||||||
|
|
||||||
|
// Test list method
|
||||||
|
print("\nListing git repositories...");
|
||||||
|
let repos = git_tree.list();
|
||||||
print(`Found ${repos.len()} repositories`);
|
print(`Found ${repos.len()} repositories`);
|
||||||
|
|
||||||
// Print the first few repositories
|
// Print the first few repositories
|
||||||
@ -17,7 +22,7 @@ if repos.len() > 0 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test find_matching_repos function
|
// Test find method
|
||||||
if repos.len() > 0 {
|
if repos.len() > 0 {
|
||||||
print("\nTesting repository search...");
|
print("\nTesting repository search...");
|
||||||
// Extract a part of the first repo name to search for
|
// Extract a part of the first repo name to search for
|
||||||
@ -26,17 +31,35 @@ if repos.len() > 0 {
|
|||||||
let repo_name = parts[parts.len() - 1];
|
let repo_name = parts[parts.len() - 1];
|
||||||
|
|
||||||
print(`Searching for repositories containing "${repo_name}"`);
|
print(`Searching for repositories containing "${repo_name}"`);
|
||||||
let matching = find_matching_repos(repo_name);
|
let matching = git_tree.find(repo_name);
|
||||||
|
|
||||||
print(`Found ${matching.len()} matching repositories`);
|
print(`Found ${matching.len()} matching repositories`);
|
||||||
for repo in matching {
|
for repo in matching {
|
||||||
print(` - ${repo}`);
|
print(` - ${repo}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test get method
|
||||||
|
print("\nTesting get method...");
|
||||||
|
let git_repos = git_tree.get(repo_name);
|
||||||
|
print(`Found ${git_repos.len()} GitRepo objects`);
|
||||||
|
|
||||||
|
// Test GitRepo methods
|
||||||
|
if git_repos.len() > 0 {
|
||||||
|
let git_repo = git_repos[0];
|
||||||
|
print(`\nTesting GitRepo methods on: ${git_repo.path()}`);
|
||||||
|
|
||||||
// Check if a repository has changes
|
// Check if a repository has changes
|
||||||
print("\nChecking for changes in repository...");
|
print("Checking for changes in repository...");
|
||||||
let has_changes = git_has_changes(repo_path);
|
let has_changes = git_repo.has_changes();
|
||||||
print(`Repository ${repo_path} has changes: ${has_changes}`);
|
print(`Repository has changes: ${has_changes}`);
|
||||||
|
|
||||||
|
// Test method chaining (only if there are no changes to avoid errors)
|
||||||
|
if !has_changes {
|
||||||
|
print("\nTesting method chaining (pull)...");
|
||||||
|
let result = git_repo.pull();
|
||||||
|
print("Pull operation completed successfully");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print("\n=== Git Module Test Complete ===");
|
print("\n=== Git Module Test Complete ===");
|
458
src/virt/buildah/builder.rs
Normal file
458
src/virt/buildah/builder.rs
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
use crate::process::CommandResult;
|
||||||
|
use crate::virt::buildah::{execute_buildah_command, BuildahError, Image};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Builder struct for buildah operations
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Builder {
|
||||||
|
/// Name of the container
|
||||||
|
name: String,
|
||||||
|
/// Container ID
|
||||||
|
container_id: Option<String>,
|
||||||
|
/// Base image
|
||||||
|
image: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
/// Create a new builder with a container from the specified image
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `name` - Name for the container
|
||||||
|
/// * `image` - Image to create the container from
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<Self, BuildahError>` - Builder instance or error
|
||||||
|
pub fn new(name: &str, image: &str) -> Result<Self, BuildahError> {
|
||||||
|
// Try to create a new container
|
||||||
|
let result = execute_buildah_command(&["from", "--name", name, image]);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(success_result) => {
|
||||||
|
// Container created successfully
|
||||||
|
let container_id = success_result.stdout.trim().to_string();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
container_id: Some(container_id),
|
||||||
|
image: image.to_string(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Err(BuildahError::CommandFailed(error_msg)) => {
|
||||||
|
// Check if the error is because the container already exists
|
||||||
|
if error_msg.contains("that name is already in use") {
|
||||||
|
// Extract the container ID from the error message
|
||||||
|
// Error format: "the container name "name" is already in use by container_id. You have to remove that container to be able to reuse that name: that name is already in use"
|
||||||
|
let container_id = error_msg
|
||||||
|
.split("already in use by ")
|
||||||
|
.nth(1)
|
||||||
|
.and_then(|s| s.split('.').next())
|
||||||
|
.unwrap_or("")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if !container_id.is_empty() {
|
||||||
|
// Container already exists, continue with it
|
||||||
|
Ok(Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
container_id: Some(container_id),
|
||||||
|
image: image.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Couldn't extract container ID
|
||||||
|
Err(BuildahError::Other("Failed to extract container ID from error message".to_string()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Other command failure
|
||||||
|
Err(BuildahError::CommandFailed(error_msg))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
// Other error
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the container ID
|
||||||
|
pub fn container_id(&self) -> Option<&String> {
|
||||||
|
self.container_id.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the container name
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the base image
|
||||||
|
pub fn image(&self) -> &str {
|
||||||
|
&self.image
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run a command in the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `command` - The command to run
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn run(&self, command: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["run", container_id, "sh", "-c", command])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run a command in the container with specified isolation
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `command` - The command to run
|
||||||
|
/// * `isolation` - Isolation method (e.g., "chroot", "rootless", "oci")
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn run_with_isolation(&self, command: &str, isolation: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["run", "--isolation", isolation, container_id, "sh", "-c", command])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy files into the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `source` - Source path
|
||||||
|
/// * `dest` - Destination path in the container
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn copy(&self, source: &str, dest: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["copy", container_id, source, dest])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add files into the container
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `source` - Source path
|
||||||
|
/// * `dest` - Destination path in the container
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn add(&self, source: &str, dest: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["add", container_id, source, dest])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commit the container to an image
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `image_name` - Name for the new image
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn commit(&self, image_name: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["commit", container_id, image_name])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the container
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn remove(&self) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
execute_buildah_command(&["rm", container_id])
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the builder by removing the container and clearing the container_id
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<(), BuildahError>` - Success or error
|
||||||
|
pub fn reset(&mut self) -> Result<(), BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
// Try to remove the container
|
||||||
|
let result = execute_buildah_command(&["rm", container_id]);
|
||||||
|
|
||||||
|
// Clear the container_id regardless of whether the removal succeeded
|
||||||
|
self.container_id = None;
|
||||||
|
|
||||||
|
// Return the result of the removal operation
|
||||||
|
match result {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No container to remove
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure container metadata
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `options` - Map of configuration options
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn config(&self, options: HashMap<String, String>) -> Result<CommandResult, BuildahError> {
|
||||||
|
if let Some(container_id) = &self.container_id {
|
||||||
|
let mut args_owned: Vec<String> = Vec::new();
|
||||||
|
args_owned.push("config".to_string());
|
||||||
|
|
||||||
|
// Process options map
|
||||||
|
for (key, value) in options.iter() {
|
||||||
|
let option_name = format!("--{}", key);
|
||||||
|
args_owned.push(option_name);
|
||||||
|
args_owned.push(value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
args_owned.push(container_id.clone());
|
||||||
|
|
||||||
|
// Convert Vec<String> to Vec<&str> for execute_buildah_command
|
||||||
|
let args: Vec<&str> = args_owned.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
|
execute_buildah_command(&args)
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::Other("No container ID available".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List images in local storage
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<Vec<Image>, BuildahError>` - List of images or error
|
||||||
|
pub fn images() -> Result<Vec<Image>, BuildahError> {
|
||||||
|
let result = execute_buildah_command(&["images", "--json"])?;
|
||||||
|
|
||||||
|
// Try to parse the JSON output
|
||||||
|
match serde_json::from_str::<serde_json::Value>(&result.stdout) {
|
||||||
|
Ok(json) => {
|
||||||
|
if let serde_json::Value::Array(images_json) = json {
|
||||||
|
let mut images = Vec::new();
|
||||||
|
|
||||||
|
for image_json in images_json {
|
||||||
|
// Extract image ID
|
||||||
|
let id = match image_json.get("id").and_then(|v| v.as_str()) {
|
||||||
|
Some(id) => id.to_string(),
|
||||||
|
None => return Err(BuildahError::ConversionError("Missing image ID".to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract image names
|
||||||
|
let names = match image_json.get("names").and_then(|v| v.as_array()) {
|
||||||
|
Some(names_array) => {
|
||||||
|
let mut names_vec = Vec::new();
|
||||||
|
for name_value in names_array {
|
||||||
|
if let Some(name_str) = name_value.as_str() {
|
||||||
|
names_vec.push(name_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names_vec
|
||||||
|
},
|
||||||
|
None => Vec::new(), // Empty vector if no names found
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract image size
|
||||||
|
let size = match image_json.get("size").and_then(|v| v.as_str()) {
|
||||||
|
Some(size) => size.to_string(),
|
||||||
|
None => "Unknown".to_string(), // Default value if size not found
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract creation timestamp
|
||||||
|
let created = match image_json.get("created").and_then(|v| v.as_str()) {
|
||||||
|
Some(created) => created.to_string(),
|
||||||
|
None => "Unknown".to_string(), // Default value if created not found
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create Image struct and add to vector
|
||||||
|
images.push(Image {
|
||||||
|
id,
|
||||||
|
names,
|
||||||
|
size,
|
||||||
|
created,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(images)
|
||||||
|
} else {
|
||||||
|
Err(BuildahError::JsonParseError("Expected JSON array".to_string()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
Err(BuildahError::JsonParseError(format!("Failed to parse image list JSON: {}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove an image
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `image` - Image ID or name
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn image_remove(image: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
execute_buildah_command(&["rmi", image])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pull an image from a registry
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `image` - Image name
|
||||||
|
/// * `tls_verify` - Whether to verify TLS
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn image_pull(image: &str, tls_verify: bool) -> Result<CommandResult, BuildahError> {
|
||||||
|
let mut args = vec!["pull"];
|
||||||
|
|
||||||
|
if !tls_verify {
|
||||||
|
args.push("--tls-verify=false");
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(image);
|
||||||
|
|
||||||
|
execute_buildah_command(&args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push an image to a registry
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `image` - Image name
|
||||||
|
/// * `destination` - Destination registry
|
||||||
|
/// * `tls_verify` - Whether to verify TLS
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn image_push(image: &str, destination: &str, tls_verify: bool) -> Result<CommandResult, BuildahError> {
|
||||||
|
let mut args = vec!["push"];
|
||||||
|
|
||||||
|
if !tls_verify {
|
||||||
|
args.push("--tls-verify=false");
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(image);
|
||||||
|
args.push(destination);
|
||||||
|
|
||||||
|
execute_buildah_command(&args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tag an image
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `image` - Image ID or name
|
||||||
|
/// * `new_name` - New tag for the image
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn image_tag(image: &str, new_name: &str) -> Result<CommandResult, BuildahError> {
|
||||||
|
execute_buildah_command(&["tag", image, new_name])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commit a container to an image with advanced options
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `container` - Container ID or name
|
||||||
|
/// * `image_name` - Name for the new image
|
||||||
|
/// * `format` - Optional format (oci or docker)
|
||||||
|
/// * `squash` - Whether to squash layers
|
||||||
|
/// * `rm` - Whether to remove the container after commit
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn image_commit(container: &str, image_name: &str, format: Option<&str>, squash: bool, rm: bool) -> Result<CommandResult, BuildahError> {
|
||||||
|
let mut args = vec!["commit"];
|
||||||
|
|
||||||
|
if let Some(format_str) = format {
|
||||||
|
args.push("--format");
|
||||||
|
args.push(format_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
if squash {
|
||||||
|
args.push("--squash");
|
||||||
|
}
|
||||||
|
|
||||||
|
if rm {
|
||||||
|
args.push("--rm");
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(container);
|
||||||
|
args.push(image_name);
|
||||||
|
|
||||||
|
execute_buildah_command(&args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build an image from a Containerfile/Dockerfile
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `tag` - Optional tag for the image
|
||||||
|
/// * `context_dir` - Directory containing the Containerfile/Dockerfile
|
||||||
|
/// * `file` - Path to the Containerfile/Dockerfile
|
||||||
|
/// * `isolation` - Optional isolation method
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<CommandResult, BuildahError>` - Command result or error
|
||||||
|
pub fn build(tag: Option<&str>, context_dir: &str, file: &str, isolation: Option<&str>) -> Result<CommandResult, BuildahError> {
|
||||||
|
let mut args = Vec::new();
|
||||||
|
args.push("build");
|
||||||
|
|
||||||
|
if let Some(tag_value) = tag {
|
||||||
|
args.push("-t");
|
||||||
|
args.push(tag_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(isolation_value) = isolation {
|
||||||
|
args.push("--isolation");
|
||||||
|
args.push(isolation_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push("-f");
|
||||||
|
args.push(file);
|
||||||
|
|
||||||
|
args.push(context_dir);
|
||||||
|
|
||||||
|
execute_buildah_command(&args)
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ mod tests {
|
|||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref LAST_COMMAND: Mutex<Vec<String>> = Mutex::new(Vec::new());
|
static ref LAST_COMMAND: Mutex<Vec<String>> = Mutex::new(Vec::new());
|
||||||
static ref SHOULD_FAIL: Mutex<bool> = Mutex::new(false);
|
static ref SHOULD_FAIL: Mutex<bool> = Mutex::new(false);
|
||||||
|
static ref TEST_MUTEX: Mutex<()> = Mutex::new(()); // Add a mutex for test synchronization
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_test_state() {
|
fn reset_test_state() {
|
||||||
@ -117,6 +118,7 @@ mod tests {
|
|||||||
// Tests for each function
|
// Tests for each function
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_function() {
|
fn test_from_function() {
|
||||||
|
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||||
reset_test_state();
|
reset_test_state();
|
||||||
|
|
||||||
let image = "alpine:latest";
|
let image = "alpine:latest";
|
||||||
@ -129,6 +131,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_run_function() {
|
fn test_run_function() {
|
||||||
|
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||||
reset_test_state();
|
reset_test_state();
|
||||||
|
|
||||||
let container = "my-container";
|
let container = "my-container";
|
||||||
@ -143,6 +146,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bah_run_with_isolation_function() {
|
fn test_bah_run_with_isolation_function() {
|
||||||
|
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||||
reset_test_state();
|
reset_test_state();
|
||||||
|
|
||||||
let container = "my-container";
|
let container = "my-container";
|
||||||
@ -157,6 +161,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bah_copy_function() {
|
fn test_bah_copy_function() {
|
||||||
|
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||||
reset_test_state();
|
reset_test_state();
|
||||||
|
|
||||||
let container = "my-container";
|
let container = "my-container";
|
||||||
@ -171,6 +176,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bah_add_function() {
|
fn test_bah_add_function() {
|
||||||
|
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||||
reset_test_state();
|
reset_test_state();
|
||||||
|
|
||||||
let container = "my-container";
|
let container = "my-container";
|
||||||
@ -185,6 +191,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bah_commit_function() {
|
fn test_bah_commit_function() {
|
||||||
|
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||||
reset_test_state();
|
reset_test_state();
|
||||||
|
|
||||||
let container = "my-container";
|
let container = "my-container";
|
||||||
@ -198,6 +205,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bah_remove_function() {
|
fn test_bah_remove_function() {
|
||||||
|
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||||
reset_test_state();
|
reset_test_state();
|
||||||
|
|
||||||
let container = "my-container";
|
let container = "my-container";
|
||||||
@ -210,6 +218,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bah_list_function() {
|
fn test_bah_list_function() {
|
||||||
|
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||||
reset_test_state();
|
reset_test_state();
|
||||||
|
|
||||||
let result = test_bah_list();
|
let result = test_bah_list();
|
||||||
@ -221,6 +230,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bah_build_function() {
|
fn test_bah_build_function() {
|
||||||
|
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||||
reset_test_state();
|
reset_test_state();
|
||||||
|
|
||||||
// Test with tag, context directory, file, and no isolation
|
// Test with tag, context directory, file, and no isolation
|
||||||
@ -229,12 +239,16 @@ mod tests {
|
|||||||
let cmd = get_last_command();
|
let cmd = get_last_command();
|
||||||
assert_eq!(cmd, vec!["build", "-t", "my-app:latest", "-f", "Dockerfile", "."]);
|
assert_eq!(cmd, vec!["build", "-t", "my-app:latest", "-f", "Dockerfile", "."]);
|
||||||
|
|
||||||
|
reset_test_state(); // Reset state between sub-tests
|
||||||
|
|
||||||
// Test with tag, context directory, file, and isolation
|
// Test with tag, context directory, file, and isolation
|
||||||
let result = test_bah_build(Some("my-app:latest"), ".", "Dockerfile.custom", Some("chroot"));
|
let result = test_bah_build(Some("my-app:latest"), ".", "Dockerfile.custom", Some("chroot"));
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let cmd = get_last_command();
|
let cmd = get_last_command();
|
||||||
assert_eq!(cmd, vec!["build", "-t", "my-app:latest", "--isolation", "chroot", "-f", "Dockerfile.custom", "."]);
|
assert_eq!(cmd, vec!["build", "-t", "my-app:latest", "--isolation", "chroot", "-f", "Dockerfile.custom", "."]);
|
||||||
|
|
||||||
|
reset_test_state(); // Reset state between sub-tests
|
||||||
|
|
||||||
// Test with just context directory and file
|
// Test with just context directory and file
|
||||||
let result = test_bah_build(None, ".", "Dockerfile", None);
|
let result = test_bah_build(None, ".", "Dockerfile", None);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
@ -244,6 +258,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_error_handling() {
|
fn test_error_handling() {
|
||||||
|
let _lock = TEST_MUTEX.lock().unwrap(); // Acquire lock for test
|
||||||
reset_test_state();
|
reset_test_state();
|
||||||
set_should_fail(true);
|
set_should_fail(true);
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
mod containers;
|
mod containers;
|
||||||
mod images;
|
mod images;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
|
mod builder;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod containers_test;
|
mod containers_test;
|
||||||
|
|
||||||
@ -43,6 +44,12 @@ impl Error for BuildahError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Re-export the Builder
|
||||||
|
pub use builder::Builder;
|
||||||
|
|
||||||
|
// Re-export existing functions for backward compatibility
|
||||||
|
#[deprecated(since = "0.2.0", note = "Use Builder::new() instead")]
|
||||||
pub use containers::*;
|
pub use containers::*;
|
||||||
|
#[deprecated(since = "0.2.0", note = "Use Builder methods instead")]
|
||||||
pub use images::*;
|
pub use images::*;
|
||||||
pub use cmd::*;
|
pub use cmd::*;
|
Loading…
Reference in New Issue
Block a user