...
This commit is contained in:
parent
7bf2ffe47d
commit
0fa9eddd1c
@ -145,6 +145,28 @@ delete("temp.txt");
|
|||||||
delete("temp_dir");
|
delete("temp_dir");
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `mv(src, dest)`
|
||||||
|
|
||||||
|
Moves a file or directory from source to destination.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `src` (string): The source path
|
||||||
|
- `dest` (string): The destination path
|
||||||
|
|
||||||
|
**Returns:** A message confirming the move was successful.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```js
|
||||||
|
// Move a file
|
||||||
|
mv("file.txt", "new_location/file.txt");
|
||||||
|
|
||||||
|
// Move a directory
|
||||||
|
mv("source_dir", "destination_dir");
|
||||||
|
|
||||||
|
// Rename a file
|
||||||
|
mv("old_name.txt", "new_name.txt");
|
||||||
|
```
|
||||||
|
|
||||||
### `mkdir(path)`
|
### `mkdir(path)`
|
||||||
|
|
||||||
Creates a directory and all parent directories. This function is defensive and doesn't error if the directory already exists.
|
Creates a directory and all parent directories. This function is defensive and doesn't error if the directory already exists.
|
||||||
|
127
src/os/fs.rs
127
src/os/fs.rs
@ -124,7 +124,16 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
for path in paths {
|
for path in paths {
|
||||||
let target_path = if dest_is_dir {
|
let target_path = if dest_is_dir {
|
||||||
// If destination is a directory, copy the file into it
|
// If destination is a directory, copy the file into it
|
||||||
|
if path.is_file() {
|
||||||
|
// For files, just use the filename
|
||||||
dest_path.join(path.file_name().unwrap_or_default())
|
dest_path.join(path.file_name().unwrap_or_default())
|
||||||
|
} else if path.is_dir() {
|
||||||
|
// For directories, use the directory name
|
||||||
|
dest_path.join(path.file_name().unwrap_or_default())
|
||||||
|
} else {
|
||||||
|
// Fallback
|
||||||
|
dest_path.join(path.file_name().unwrap_or_default())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise use the destination as is (only makes sense for single file)
|
// Otherwise use the destination as is (only makes sense for single file)
|
||||||
dest_path.to_path_buf()
|
dest_path.to_path_buf()
|
||||||
@ -186,9 +195,17 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
|||||||
|
|
||||||
// Copy based on source type
|
// Copy based on source type
|
||||||
if src_path.is_file() {
|
if src_path.is_file() {
|
||||||
// Copy file
|
// If destination is a directory, copy the file into it
|
||||||
|
if dest_path.exists() && dest_path.is_dir() {
|
||||||
|
let file_name = src_path.file_name().unwrap_or_default();
|
||||||
|
let new_dest_path = dest_path.join(file_name);
|
||||||
|
fs::copy(src_path, new_dest_path).map_err(FsError::CopyFailed)?;
|
||||||
|
Ok(format!("Successfully copied file '{}' to '{}/{}'", src, dest, file_name.to_string_lossy()))
|
||||||
|
} else {
|
||||||
|
// Otherwise copy file to the specified destination
|
||||||
fs::copy(src_path, dest_path).map_err(FsError::CopyFailed)?;
|
fs::copy(src_path, dest_path).map_err(FsError::CopyFailed)?;
|
||||||
Ok(format!("Successfully copied file '{}' to '{}'", src, dest))
|
Ok(format!("Successfully copied file '{}' to '{}'", src, dest))
|
||||||
|
}
|
||||||
} else if src_path.is_dir() {
|
} else if src_path.is_dir() {
|
||||||
// For directories, use platform-specific command
|
// For directories, use platform-specific command
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@ -743,6 +760,114 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
|
|||||||
Ok(format!("Successfully appended to file '{}'", path))
|
Ok(format!("Successfully appended to file '{}'", path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a file or directory from source to destination.
|
||||||
|
*
|
||||||
|
* # Arguments
|
||||||
|
*
|
||||||
|
* * `src` - The source path
|
||||||
|
* * `dest` - The destination path
|
||||||
|
*
|
||||||
|
* # Returns
|
||||||
|
*
|
||||||
|
* * `Ok(String)` - A success message indicating what was moved
|
||||||
|
* * `Err(FsError)` - An error if the move operation failed
|
||||||
|
*
|
||||||
|
* # Examples
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* // Move a file
|
||||||
|
* let result = mv("file.txt", "new_location/file.txt")?;
|
||||||
|
*
|
||||||
|
* // Move a directory
|
||||||
|
* let result = mv("src_dir", "dest_dir")?;
|
||||||
|
*
|
||||||
|
* // Rename a file
|
||||||
|
* let result = mv("old_name.txt", "new_name.txt")?;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
||||||
|
let src_path = Path::new(src);
|
||||||
|
let dest_path = Path::new(dest);
|
||||||
|
|
||||||
|
// Check if source exists
|
||||||
|
if !src_path.exists() {
|
||||||
|
return Err(FsError::FileNotFound(src.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create parent directories if they don't exist
|
||||||
|
if let Some(parent) = dest_path.parent() {
|
||||||
|
fs::create_dir_all(parent).map_err(FsError::CreateDirectoryFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the case where destination is a directory and exists
|
||||||
|
let final_dest_path = if dest_path.exists() && dest_path.is_dir() && src_path.is_file() {
|
||||||
|
// If destination is a directory and source is a file, move the file into the directory
|
||||||
|
let file_name = src_path.file_name().unwrap_or_default();
|
||||||
|
dest_path.join(file_name)
|
||||||
|
} else {
|
||||||
|
dest_path.to_path_buf()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clone the path for use in the error handler
|
||||||
|
let final_dest_path_clone = final_dest_path.clone();
|
||||||
|
|
||||||
|
// Perform the move operation
|
||||||
|
fs::rename(src_path, &final_dest_path).map_err(|e| {
|
||||||
|
// If rename fails (possibly due to cross-device link), try copy and delete
|
||||||
|
if e.kind() == std::io::ErrorKind::CrossesDevices {
|
||||||
|
// For cross-device moves, we need to copy and then delete
|
||||||
|
if src_path.is_file() {
|
||||||
|
// Copy file
|
||||||
|
match fs::copy(src_path, &final_dest_path_clone) {
|
||||||
|
Ok(_) => {
|
||||||
|
// Delete source after successful copy
|
||||||
|
if let Err(del_err) = fs::remove_file(src_path) {
|
||||||
|
return FsError::DeleteFailed(del_err);
|
||||||
|
}
|
||||||
|
return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message
|
||||||
|
},
|
||||||
|
Err(copy_err) => return FsError::CopyFailed(copy_err),
|
||||||
|
}
|
||||||
|
} else if src_path.is_dir() {
|
||||||
|
// For directories, use platform-specific command
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let output = Command::new("xcopy")
|
||||||
|
.args(&["/E", "/I", "/H", "/Y", src, dest])
|
||||||
|
.status();
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let output = Command::new("cp")
|
||||||
|
.args(&["-R", src, dest])
|
||||||
|
.status();
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(status) => {
|
||||||
|
if status.success() {
|
||||||
|
// Delete source after successful copy
|
||||||
|
if let Err(del_err) = fs::remove_dir_all(src_path) {
|
||||||
|
return FsError::DeleteFailed(del_err);
|
||||||
|
}
|
||||||
|
return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message
|
||||||
|
} else {
|
||||||
|
return FsError::CommandFailed("Failed to copy directory for move operation".to_string());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(cmd_err) => return FsError::CommandExecutionError(cmd_err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FsError::CommandFailed(format!("Failed to move '{}' to '{}': {}", src, dest, e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// If we get here, either the rename was successful or our copy-delete hack worked
|
||||||
|
if src_path.is_file() {
|
||||||
|
Ok(format!("Successfully moved file '{}' to '{}'", src, dest))
|
||||||
|
} else {
|
||||||
|
Ok(format!("Successfully moved directory '{}' to '{}'", src, dest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a command exists in the system PATH.
|
* Check if a command exists in the system PATH.
|
||||||
*
|
*
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
mod fs;
|
mod fs;
|
||||||
mod download;
|
mod download;
|
||||||
|
pub mod package;
|
||||||
|
|
||||||
pub use fs::*;
|
pub use fs::*;
|
||||||
pub use download::*;
|
pub use download::*;
|
||||||
|
pub use package::*;
|
421
src/os/package.rs
Normal file
421
src/os/package.rs
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
use crate::process::CommandResult;
|
||||||
|
|
||||||
|
/// Error type for package management operations
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PackageError {
|
||||||
|
/// Command failed with error message
|
||||||
|
CommandFailed(String),
|
||||||
|
/// Command execution failed with IO error
|
||||||
|
CommandExecutionFailed(std::io::Error),
|
||||||
|
/// Unsupported platform
|
||||||
|
UnsupportedPlatform(String),
|
||||||
|
/// Other error
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for PackageError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
PackageError::CommandFailed(msg) => write!(f, "Command failed: {}", msg),
|
||||||
|
PackageError::CommandExecutionFailed(e) => write!(f, "Command execution failed: {}", e),
|
||||||
|
PackageError::UnsupportedPlatform(msg) => write!(f, "Unsupported platform: {}", msg),
|
||||||
|
PackageError::Other(msg) => write!(f, "Error: {}", msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for PackageError {}
|
||||||
|
|
||||||
|
/// Platform enum for detecting the current operating system
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum Platform {
|
||||||
|
/// Ubuntu Linux
|
||||||
|
Ubuntu,
|
||||||
|
/// macOS
|
||||||
|
MacOS,
|
||||||
|
/// Unknown platform
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Platform {
|
||||||
|
/// Detect the current platform
|
||||||
|
pub fn detect() -> Self {
|
||||||
|
// Check for macOS
|
||||||
|
if std::path::Path::new("/usr/bin/sw_vers").exists() {
|
||||||
|
return Platform::MacOS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Ubuntu
|
||||||
|
if std::path::Path::new("/etc/lsb-release").exists() {
|
||||||
|
// Read the file to confirm it's Ubuntu
|
||||||
|
if let Ok(content) = std::fs::read_to_string("/etc/lsb-release") {
|
||||||
|
if content.contains("Ubuntu") {
|
||||||
|
return Platform::Ubuntu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Thread-local storage for debug flag
|
||||||
|
thread_local! {
|
||||||
|
static DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the debug flag for the current thread
|
||||||
|
pub fn set_thread_local_debug(debug: bool) {
|
||||||
|
DEBUG.with(|cell| {
|
||||||
|
*cell.borrow_mut() = debug;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the debug flag for the current thread
|
||||||
|
pub fn thread_local_debug() -> bool {
|
||||||
|
DEBUG.with(|cell| {
|
||||||
|
*cell.borrow()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a package management command and return the result
|
||||||
|
pub fn execute_package_command(args: &[&str], debug: bool) -> Result<CommandResult, PackageError> {
|
||||||
|
// Save the current debug flag
|
||||||
|
let previous_debug = thread_local_debug();
|
||||||
|
|
||||||
|
// Set the thread-local debug flag
|
||||||
|
set_thread_local_debug(debug);
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
println!("Executing command: {}", args.join(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new(args[0])
|
||||||
|
.args(&args[1..])
|
||||||
|
.output();
|
||||||
|
|
||||||
|
// Restore the previous debug flag
|
||||||
|
set_thread_local_debug(previous_debug);
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(output) => {
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||||
|
|
||||||
|
let result = CommandResult {
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
success: output.status.success(),
|
||||||
|
code: output.status.code().unwrap_or(-1),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Always output stdout/stderr when debug is true
|
||||||
|
if debug {
|
||||||
|
if !result.stdout.is_empty() {
|
||||||
|
println!("Command stdout: {}", result.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result.stderr.is_empty() {
|
||||||
|
println!("Command stderr: {}", result.stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.success {
|
||||||
|
println!("Command succeeded with code {}", result.code);
|
||||||
|
} else {
|
||||||
|
println!("Command failed with code {}", result.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.success {
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
// If command failed and debug is false, output stderr
|
||||||
|
if !debug {
|
||||||
|
println!("Command failed with code {}: {}", result.code, result.stderr.trim());
|
||||||
|
}
|
||||||
|
Err(PackageError::CommandFailed(format!("Command failed with code {}: {}",
|
||||||
|
result.code, result.stderr.trim())))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
// Always output error information
|
||||||
|
println!("Command execution failed: {}", e);
|
||||||
|
Err(PackageError::CommandExecutionFailed(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for package managers
|
||||||
|
pub trait PackageManager {
|
||||||
|
/// Install a package
|
||||||
|
fn install(&self, package: &str) -> Result<CommandResult, PackageError>;
|
||||||
|
|
||||||
|
/// Remove a package
|
||||||
|
fn remove(&self, package: &str) -> Result<CommandResult, PackageError>;
|
||||||
|
|
||||||
|
/// Update package lists
|
||||||
|
fn update(&self) -> Result<CommandResult, PackageError>;
|
||||||
|
|
||||||
|
/// Upgrade installed packages
|
||||||
|
fn upgrade(&self) -> Result<CommandResult, PackageError>;
|
||||||
|
|
||||||
|
/// List installed packages
|
||||||
|
fn list_installed(&self) -> Result<Vec<String>, PackageError>;
|
||||||
|
|
||||||
|
/// Search for packages
|
||||||
|
fn search(&self, query: &str) -> Result<Vec<String>, PackageError>;
|
||||||
|
|
||||||
|
/// Check if a package is installed
|
||||||
|
fn is_installed(&self, package: &str) -> Result<bool, PackageError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// APT package manager for Ubuntu
|
||||||
|
pub struct AptPackageManager {
|
||||||
|
debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AptPackageManager {
|
||||||
|
/// Create a new APT package manager
|
||||||
|
pub fn new(debug: bool) -> Self {
|
||||||
|
Self { debug }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageManager for AptPackageManager {
|
||||||
|
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
// Use -y to make it non-interactive and --quiet to reduce output
|
||||||
|
execute_package_command(&["apt-get", "install", "-y", "--quiet", package], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
// Use -y to make it non-interactive and --quiet to reduce output
|
||||||
|
execute_package_command(&["apt-get", "remove", "-y", "--quiet", package], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
// Use -y to make it non-interactive and --quiet to reduce output
|
||||||
|
execute_package_command(&["apt-get", "update", "-y", "--quiet"], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
// Use -y to make it non-interactive and --quiet to reduce output
|
||||||
|
execute_package_command(&["apt-get", "upgrade", "-y", "--quiet"], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||||
|
let result = execute_package_command(&["dpkg", "--get-selections"], self.debug)?;
|
||||||
|
let packages = result.stdout
|
||||||
|
.lines()
|
||||||
|
.filter_map(|line| {
|
||||||
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
|
if parts.len() >= 2 && parts[1] == "install" {
|
||||||
|
Some(parts[0].to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||||
|
let result = execute_package_command(&["apt-cache", "search", query], self.debug)?;
|
||||||
|
let packages = result.stdout
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
|
if !parts.is_empty() {
|
||||||
|
parts[0].to_string()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
Ok(packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||||
|
let result = execute_package_command(&["dpkg", "-s", package], self.debug);
|
||||||
|
match result {
|
||||||
|
Ok(cmd_result) => Ok(cmd_result.success),
|
||||||
|
Err(_) => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Homebrew package manager for macOS
|
||||||
|
pub struct BrewPackageManager {
|
||||||
|
debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BrewPackageManager {
|
||||||
|
/// Create a new Homebrew package manager
|
||||||
|
pub fn new(debug: bool) -> Self {
|
||||||
|
Self { debug }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageManager for BrewPackageManager {
|
||||||
|
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
// Use --quiet to reduce output
|
||||||
|
execute_package_command(&["brew", "install", "--quiet", package], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
// Use --quiet to reduce output
|
||||||
|
execute_package_command(&["brew", "uninstall", "--quiet", package], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
// Use --quiet to reduce output
|
||||||
|
execute_package_command(&["brew", "update", "--quiet"], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
// Use --quiet to reduce output
|
||||||
|
execute_package_command(&["brew", "upgrade", "--quiet"], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||||
|
let result = execute_package_command(&["brew", "list", "--formula"], self.debug)?;
|
||||||
|
let packages = result.stdout
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
Ok(packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||||
|
let result = execute_package_command(&["brew", "search", query], self.debug)?;
|
||||||
|
let packages = result.stdout
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
Ok(packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||||
|
let result = execute_package_command(&["brew", "list", package], self.debug);
|
||||||
|
match result {
|
||||||
|
Ok(cmd_result) => Ok(cmd_result.success),
|
||||||
|
Err(_) => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PackHero factory for package management
|
||||||
|
pub struct PackHero {
|
||||||
|
platform: Platform,
|
||||||
|
debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackHero {
|
||||||
|
/// Create a new PackHero instance
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let platform = Platform::detect();
|
||||||
|
Self {
|
||||||
|
platform,
|
||||||
|
debug: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the debug mode
|
||||||
|
pub fn set_debug(&mut self, debug: bool) -> &mut Self {
|
||||||
|
self.debug = debug;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the debug mode
|
||||||
|
pub fn debug(&self) -> bool {
|
||||||
|
self.debug
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the detected platform
|
||||||
|
pub fn platform(&self) -> Platform {
|
||||||
|
self.platform
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a package manager for the current platform
|
||||||
|
fn get_package_manager(&self) -> Result<Box<dyn PackageManager>, PackageError> {
|
||||||
|
match self.platform {
|
||||||
|
Platform::Ubuntu => Ok(Box::new(AptPackageManager::new(self.debug))),
|
||||||
|
Platform::MacOS => Ok(Box::new(BrewPackageManager::new(self.debug))),
|
||||||
|
Platform::Unknown => Err(PackageError::UnsupportedPlatform("Unsupported platform".to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install a package
|
||||||
|
pub fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.install(package)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a package
|
||||||
|
pub fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.remove(package)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update package lists
|
||||||
|
pub fn update(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upgrade installed packages
|
||||||
|
pub fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.upgrade()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List installed packages
|
||||||
|
pub fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.list_installed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search for packages
|
||||||
|
pub fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.search(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a package is installed
|
||||||
|
pub fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.is_installed(package)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_platform_detection() {
|
||||||
|
// This test will return different results depending on the platform it's run on
|
||||||
|
let platform = Platform::detect();
|
||||||
|
println!("Detected platform: {:?}", platform);
|
||||||
|
|
||||||
|
// Just ensure it doesn't panic
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_debug_flag() {
|
||||||
|
// Test setting and getting the debug flag
|
||||||
|
set_thread_local_debug(true);
|
||||||
|
assert_eq!(thread_local_debug(), true);
|
||||||
|
|
||||||
|
set_thread_local_debug(false);
|
||||||
|
assert_eq!(thread_local_debug(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// More tests would be added for each platform-specific implementation
|
||||||
|
// These would likely be integration tests that are conditionally compiled
|
||||||
|
// based on the platform they're running on
|
||||||
|
}
|
565
src/package_implementation_plan.md
Normal file
565
src/package_implementation_plan.md
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
# Package Management Module Implementation Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The package management module will:
|
||||||
|
1. Provide a factory called `PackHero` that detects the current platform
|
||||||
|
2. Implement platform-specific package managers for Ubuntu (apt) and macOS (brew)
|
||||||
|
3. Support operations: install, remove, update, upgrade, list installed packages, search for packages, and check if a package is installed
|
||||||
|
4. Include debug functionality similar to buildah
|
||||||
|
5. Ensure all operations are non-interactive and have proper error propagation
|
||||||
|
6. Be wrapped in Rhai for scripting access
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class PackageError {
|
||||||
|
+CommandFailed(String)
|
||||||
|
+CommandExecutionFailed(std::io::Error)
|
||||||
|
+UnsupportedPlatform(String)
|
||||||
|
+Other(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
class PackHero {
|
||||||
|
-platform: Platform
|
||||||
|
-debug: bool
|
||||||
|
+new() PackHero
|
||||||
|
+detect_platform() Platform
|
||||||
|
+set_debug(bool) PackHero
|
||||||
|
+debug() bool
|
||||||
|
+install(package: &str) Result
|
||||||
|
+remove(package: &str) Result
|
||||||
|
+update() Result
|
||||||
|
+upgrade() Result
|
||||||
|
+list_installed() Result
|
||||||
|
+search(query: &str) Result
|
||||||
|
+is_installed(package: &str) Result
|
||||||
|
}
|
||||||
|
|
||||||
|
class Platform {
|
||||||
|
<<enumeration>>
|
||||||
|
Ubuntu
|
||||||
|
MacOS
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
class PackageManager {
|
||||||
|
<<interface>>
|
||||||
|
+install(package: &str) Result
|
||||||
|
+remove(package: &str) Result
|
||||||
|
+update() Result
|
||||||
|
+upgrade() Result
|
||||||
|
+list_installed() Result
|
||||||
|
+search(query: &str) Result
|
||||||
|
+is_installed(package: &str) Result
|
||||||
|
}
|
||||||
|
|
||||||
|
class AptPackageManager {
|
||||||
|
-debug: bool
|
||||||
|
+new(debug: bool) AptPackageManager
|
||||||
|
+install(package: &str) Result
|
||||||
|
+remove(package: &str) Result
|
||||||
|
+update() Result
|
||||||
|
+upgrade() Result
|
||||||
|
+list_installed() Result
|
||||||
|
+search(query: &str) Result
|
||||||
|
+is_installed(package: &str) Result
|
||||||
|
}
|
||||||
|
|
||||||
|
class BrewPackageManager {
|
||||||
|
-debug: bool
|
||||||
|
+new(debug: bool) BrewPackageManager
|
||||||
|
+install(package: &str) Result
|
||||||
|
+remove(package: &str) Result
|
||||||
|
+update() Result
|
||||||
|
+upgrade() Result
|
||||||
|
+list_installed() Result
|
||||||
|
+search(query: &str) Result
|
||||||
|
+is_installed(package: &str) Result
|
||||||
|
}
|
||||||
|
|
||||||
|
PackHero --> Platform : uses
|
||||||
|
PackHero --> PackageManager : uses
|
||||||
|
PackageManager <|.. AptPackageManager : implements
|
||||||
|
PackageManager <|.. BrewPackageManager : implements
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### 1. Package Error Type
|
||||||
|
|
||||||
|
Create a custom error type for package management operations:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum PackageError {
|
||||||
|
CommandFailed(String),
|
||||||
|
CommandExecutionFailed(std::io::Error),
|
||||||
|
UnsupportedPlatform(String),
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Platform Detection
|
||||||
|
|
||||||
|
Implement platform detection to determine which package manager to use:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum Platform {
|
||||||
|
Ubuntu,
|
||||||
|
MacOS,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Platform {
|
||||||
|
pub fn detect() -> Self {
|
||||||
|
// Check for macOS
|
||||||
|
if std::path::Path::new("/usr/bin/sw_vers").exists() {
|
||||||
|
return Platform::MacOS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Ubuntu
|
||||||
|
if std::path::Path::new("/etc/lsb-release").exists() {
|
||||||
|
// Read the file to confirm it's Ubuntu
|
||||||
|
if let Ok(content) = std::fs::read_to_string("/etc/lsb-release") {
|
||||||
|
if content.contains("Ubuntu") {
|
||||||
|
return Platform::Ubuntu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Package Manager Trait
|
||||||
|
|
||||||
|
Define a trait for package managers to implement:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait PackageManager {
|
||||||
|
fn install(&self, package: &str) -> Result<CommandResult, PackageError>;
|
||||||
|
fn remove(&self, package: &str) -> Result<CommandResult, PackageError>;
|
||||||
|
fn update(&self) -> Result<CommandResult, PackageError>;
|
||||||
|
fn upgrade(&self) -> Result<CommandResult, PackageError>;
|
||||||
|
fn list_installed(&self) -> Result<Vec<String>, PackageError>;
|
||||||
|
fn search(&self, query: &str) -> Result<Vec<String>, PackageError>;
|
||||||
|
fn is_installed(&self, package: &str) -> Result<bool, PackageError>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Platform-Specific Implementations
|
||||||
|
|
||||||
|
#### Ubuntu (apt) Implementation
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct AptPackageManager {
|
||||||
|
debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AptPackageManager {
|
||||||
|
pub fn new(debug: bool) -> Self {
|
||||||
|
Self { debug }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageManager for AptPackageManager {
|
||||||
|
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
execute_package_command(&["apt-get", "install", "-y", package], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
execute_package_command(&["apt-get", "remove", "-y", package], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
execute_package_command(&["apt-get", "update", "-y"], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
execute_package_command(&["apt-get", "upgrade", "-y"], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||||
|
let result = execute_package_command(&["dpkg", "--get-selections"], self.debug)?;
|
||||||
|
let packages = result.stdout
|
||||||
|
.lines()
|
||||||
|
.filter_map(|line| {
|
||||||
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
|
if parts.len() >= 2 && parts[1] == "install" {
|
||||||
|
Some(parts[0].to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||||
|
let result = execute_package_command(&["apt-cache", "search", query], self.debug)?;
|
||||||
|
let packages = result.stdout
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
|
if !parts.is_empty() {
|
||||||
|
parts[0].to_string()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
Ok(packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||||
|
let result = execute_package_command(&["dpkg", "-s", package], self.debug);
|
||||||
|
match result {
|
||||||
|
Ok(cmd_result) => Ok(cmd_result.success),
|
||||||
|
Err(_) => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### macOS (brew) Implementation
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct BrewPackageManager {
|
||||||
|
debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BrewPackageManager {
|
||||||
|
pub fn new(debug: bool) -> Self {
|
||||||
|
Self { debug }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageManager for BrewPackageManager {
|
||||||
|
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
execute_package_command(&["brew", "install", package], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
execute_package_command(&["brew", "uninstall", package], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
execute_package_command(&["brew", "update"], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
execute_package_command(&["brew", "upgrade"], self.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||||
|
let result = execute_package_command(&["brew", "list", "--formula"], self.debug)?;
|
||||||
|
let packages = result.stdout
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
Ok(packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||||
|
let result = execute_package_command(&["brew", "search", query], self.debug)?;
|
||||||
|
let packages = result.stdout
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
Ok(packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||||
|
let result = execute_package_command(&["brew", "list", package], self.debug);
|
||||||
|
match result {
|
||||||
|
Ok(cmd_result) => Ok(cmd_result.success),
|
||||||
|
Err(_) => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Command Execution with Debug Support
|
||||||
|
|
||||||
|
Implement a function to execute package management commands with debug support:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Thread-local storage for debug flag
|
||||||
|
thread_local! {
|
||||||
|
static DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the debug flag for the current thread
|
||||||
|
pub fn set_thread_local_debug(debug: bool) {
|
||||||
|
DEBUG.with(|cell| {
|
||||||
|
*cell.borrow_mut() = debug;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the debug flag for the current thread
|
||||||
|
pub fn thread_local_debug() -> bool {
|
||||||
|
DEBUG.with(|cell| {
|
||||||
|
*cell.borrow()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a package management command and return the result
|
||||||
|
pub fn execute_package_command(args: &[&str], debug: bool) -> Result<CommandResult, PackageError> {
|
||||||
|
// Save the current debug flag
|
||||||
|
let previous_debug = thread_local_debug();
|
||||||
|
|
||||||
|
// Set the thread-local debug flag
|
||||||
|
set_thread_local_debug(debug);
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
println!("Executing command: {}", args.join(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new(args[0])
|
||||||
|
.args(&args[1..])
|
||||||
|
.output();
|
||||||
|
|
||||||
|
// Restore the previous debug flag
|
||||||
|
set_thread_local_debug(previous_debug);
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(output) => {
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||||
|
|
||||||
|
let result = CommandResult {
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
success: output.status.success(),
|
||||||
|
code: output.status.code().unwrap_or(-1),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Always output stdout/stderr when debug is true
|
||||||
|
if debug {
|
||||||
|
if !result.stdout.is_empty() {
|
||||||
|
println!("Command stdout: {}", result.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result.stderr.is_empty() {
|
||||||
|
println!("Command stderr: {}", result.stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.success {
|
||||||
|
println!("Command succeeded with code {}", result.code);
|
||||||
|
} else {
|
||||||
|
println!("Command failed with code {}", result.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.success {
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
// If command failed and debug is false, output stderr
|
||||||
|
if !debug {
|
||||||
|
println!("Command failed with code {}: {}", result.code, result.stderr.trim());
|
||||||
|
}
|
||||||
|
Err(PackageError::CommandFailed(format!("Command failed with code {}: {}",
|
||||||
|
result.code, result.stderr.trim())))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
// Always output error information
|
||||||
|
println!("Command execution failed: {}", e);
|
||||||
|
Err(PackageError::CommandExecutionFailed(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. PackHero Factory
|
||||||
|
|
||||||
|
Implement the PackHero factory to provide a unified interface:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PackHero {
|
||||||
|
platform: Platform,
|
||||||
|
debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackHero {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let platform = Platform::detect();
|
||||||
|
Self {
|
||||||
|
platform,
|
||||||
|
debug: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_debug(&mut self, debug: bool) -> &mut Self {
|
||||||
|
self.debug = debug;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debug(&self) -> bool {
|
||||||
|
self.debug
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_package_manager(&self) -> Result<Box<dyn PackageManager>, PackageError> {
|
||||||
|
match self.platform {
|
||||||
|
Platform::Ubuntu => Ok(Box::new(AptPackageManager::new(self.debug))),
|
||||||
|
Platform::MacOS => Ok(Box::new(BrewPackageManager::new(self.debug))),
|
||||||
|
Platform::Unknown => Err(PackageError::UnsupportedPlatform("Unsupported platform".to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.install(package)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.remove(package)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.upgrade()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.list_installed()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.search(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||||
|
let pm = self.get_package_manager()?;
|
||||||
|
pm.is_installed(package)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Rhai Integration
|
||||||
|
|
||||||
|
Update the Rhai OS module to include the package management functions:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// In rhai/os.rs
|
||||||
|
|
||||||
|
// Register package management functions
|
||||||
|
engine.register_fn("package_install", package_install);
|
||||||
|
engine.register_fn("package_remove", package_remove);
|
||||||
|
engine.register_fn("package_update", package_update);
|
||||||
|
engine.register_fn("package_upgrade", package_upgrade);
|
||||||
|
engine.register_fn("package_list", package_list);
|
||||||
|
engine.register_fn("package_search", package_search);
|
||||||
|
engine.register_fn("package_is_installed", package_is_installed);
|
||||||
|
engine.register_fn("package_set_debug", package_set_debug);
|
||||||
|
|
||||||
|
// Wrapper for os::package::install
|
||||||
|
pub fn package_install(package: &str) -> Result<String, Box<EvalAltResult>> {
|
||||||
|
let hero = os::package::PackHero::new();
|
||||||
|
hero.install(package)
|
||||||
|
.map(|_| "Package installed successfully".to_string())
|
||||||
|
.to_rhai_error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper for os::package::remove
|
||||||
|
pub fn package_remove(package: &str) -> Result<String, Box<EvalAltResult>> {
|
||||||
|
let hero = os::package::PackHero::new();
|
||||||
|
hero.remove(package)
|
||||||
|
.map(|_| "Package removed successfully".to_string())
|
||||||
|
.to_rhai_error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper for os::package::update
|
||||||
|
pub fn package_update() -> Result<String, Box<EvalAltResult>> {
|
||||||
|
let hero = os::package::PackHero::new();
|
||||||
|
hero.update()
|
||||||
|
.map(|_| "Package lists updated successfully".to_string())
|
||||||
|
.to_rhai_error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper for os::package::upgrade
|
||||||
|
pub fn package_upgrade() -> Result<String, Box<EvalAltResult>> {
|
||||||
|
let hero = os::package::PackHero::new();
|
||||||
|
hero.upgrade()
|
||||||
|
.map(|_| "Packages upgraded successfully".to_string())
|
||||||
|
.to_rhai_error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper for os::package::list_installed
|
||||||
|
pub fn package_list() -> Result<Array, Box<EvalAltResult>> {
|
||||||
|
let hero = os::package::PackHero::new();
|
||||||
|
let packages = hero.list_installed().to_rhai_error()?;
|
||||||
|
|
||||||
|
// Convert Vec<String> to Rhai Array
|
||||||
|
let mut array = Array::new();
|
||||||
|
for package in packages {
|
||||||
|
array.push(package.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper for os::package::search
|
||||||
|
pub fn package_search(query: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||||
|
let hero = os::package::PackHero::new();
|
||||||
|
let packages = hero.search(query).to_rhai_error()?;
|
||||||
|
|
||||||
|
// Convert Vec<String> to Rhai Array
|
||||||
|
let mut array = Array::new();
|
||||||
|
for package in packages {
|
||||||
|
array.push(package.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper for os::package::is_installed
|
||||||
|
pub fn package_is_installed(package: &str) -> Result<bool, Box<EvalAltResult>> {
|
||||||
|
let hero = os::package::PackHero::new();
|
||||||
|
hero.is_installed(package).to_rhai_error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global debug flag for package management
|
||||||
|
static mut PACKAGE_DEBUG: bool = false;
|
||||||
|
|
||||||
|
// Wrapper for setting package debug mode
|
||||||
|
pub fn package_set_debug(debug: bool) -> bool {
|
||||||
|
unsafe {
|
||||||
|
PACKAGE_DEBUG = debug;
|
||||||
|
PACKAGE_DEBUG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. Create the package.rs file in the os directory
|
||||||
|
2. Implement the PackageError enum
|
||||||
|
3. Implement the Platform enum with detection logic
|
||||||
|
4. Implement the PackageManager trait
|
||||||
|
5. Implement the AptPackageManager for Ubuntu
|
||||||
|
6. Implement the BrewPackageManager for macOS
|
||||||
|
7. Implement the debug functionality with thread-local storage
|
||||||
|
8. Implement the PackHero factory
|
||||||
|
9. Update os/mod.rs to include the package module
|
||||||
|
10. Update rhai/os.rs to include the package management functions
|
||||||
|
11. Test the implementation on both Ubuntu and macOS
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
1. Create unit tests for each platform-specific implementation
|
||||||
|
2. Create integration tests that verify the PackHero factory correctly detects the platform and uses the appropriate package manager
|
||||||
|
3. Create Rhai examples that demonstrate the use of the package management functions
|
134
src/rhai/os.rs
134
src/rhai/os.rs
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, Array};
|
use rhai::{Engine, EvalAltResult, Array};
|
||||||
use crate::os;
|
use crate::os;
|
||||||
|
use crate::os::package::PackHero;
|
||||||
use super::error::{ToRhaiError, register_error_types};
|
use super::error::{ToRhaiError, register_error_types};
|
||||||
|
|
||||||
/// Register OS module functions with the Rhai engine
|
/// Register OS module functions with the Rhai engine
|
||||||
@ -45,6 +46,20 @@ pub fn register_os_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>>
|
|||||||
engine.register_fn("download_install", download_install);
|
engine.register_fn("download_install", download_install);
|
||||||
engine.register_fn("chmod_exec", chmod_exec);
|
engine.register_fn("chmod_exec", chmod_exec);
|
||||||
|
|
||||||
|
// Register move function
|
||||||
|
engine.register_fn("mv", mv);
|
||||||
|
|
||||||
|
// Register package management functions
|
||||||
|
engine.register_fn("package_install", package_install);
|
||||||
|
engine.register_fn("package_remove", package_remove);
|
||||||
|
engine.register_fn("package_update", package_update);
|
||||||
|
engine.register_fn("package_upgrade", package_upgrade);
|
||||||
|
engine.register_fn("package_list", package_list);
|
||||||
|
engine.register_fn("package_search", package_search);
|
||||||
|
engine.register_fn("package_is_installed", package_is_installed);
|
||||||
|
engine.register_fn("package_set_debug", package_set_debug);
|
||||||
|
engine.register_fn("package_platform", package_platform);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +181,13 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, Box<EvalAl
|
|||||||
os::file_write_append(path, content).to_rhai_error()
|
os::file_write_append(path, content).to_rhai_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrapper for os::mv
|
||||||
|
///
|
||||||
|
/// Move a file or directory from source to destination.
|
||||||
|
pub fn mv(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
|
||||||
|
os::mv(src, dest).to_rhai_error()
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Download Function Wrappers
|
// Download Function Wrappers
|
||||||
//
|
//
|
||||||
@ -212,3 +234,115 @@ pub fn which(command: &str) -> String {
|
|||||||
pub fn cmd_ensure_exists(commands: &str) -> Result<String, Box<EvalAltResult>> {
|
pub fn cmd_ensure_exists(commands: &str) -> Result<String, Box<EvalAltResult>> {
|
||||||
os::cmd_ensure_exists(commands).to_rhai_error()
|
os::cmd_ensure_exists(commands).to_rhai_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Package Management Function Wrappers
|
||||||
|
//
|
||||||
|
|
||||||
|
/// Wrapper for os::package::PackHero::install
|
||||||
|
///
|
||||||
|
/// Install a package using the system package manager.
|
||||||
|
pub fn package_install(package: &str) -> Result<String, Box<EvalAltResult>> {
|
||||||
|
let hero = PackHero::new();
|
||||||
|
hero.install(package)
|
||||||
|
.map(|_| format!("Package '{}' installed successfully", package))
|
||||||
|
.to_rhai_error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for os::package::PackHero::remove
|
||||||
|
///
|
||||||
|
/// Remove a package using the system package manager.
|
||||||
|
pub fn package_remove(package: &str) -> Result<String, Box<EvalAltResult>> {
|
||||||
|
let hero = PackHero::new();
|
||||||
|
hero.remove(package)
|
||||||
|
.map(|_| format!("Package '{}' removed successfully", package))
|
||||||
|
.to_rhai_error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for os::package::PackHero::update
|
||||||
|
///
|
||||||
|
/// Update package lists using the system package manager.
|
||||||
|
pub fn package_update() -> Result<String, Box<EvalAltResult>> {
|
||||||
|
let hero = PackHero::new();
|
||||||
|
hero.update()
|
||||||
|
.map(|_| "Package lists updated successfully".to_string())
|
||||||
|
.to_rhai_error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for os::package::PackHero::upgrade
|
||||||
|
///
|
||||||
|
/// Upgrade installed packages using the system package manager.
|
||||||
|
pub fn package_upgrade() -> Result<String, Box<EvalAltResult>> {
|
||||||
|
let hero = PackHero::new();
|
||||||
|
hero.upgrade()
|
||||||
|
.map(|_| "Packages upgraded successfully".to_string())
|
||||||
|
.to_rhai_error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for os::package::PackHero::list_installed
|
||||||
|
///
|
||||||
|
/// List installed packages using the system package manager.
|
||||||
|
pub fn package_list() -> Result<Array, Box<EvalAltResult>> {
|
||||||
|
let hero = PackHero::new();
|
||||||
|
let packages = hero.list_installed().to_rhai_error()?;
|
||||||
|
|
||||||
|
// Convert Vec<String> to Rhai Array
|
||||||
|
let mut array = Array::new();
|
||||||
|
for package in packages {
|
||||||
|
array.push(package.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for os::package::PackHero::search
|
||||||
|
///
|
||||||
|
/// Search for packages using the system package manager.
|
||||||
|
pub fn package_search(query: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||||
|
let hero = PackHero::new();
|
||||||
|
let packages = hero.search(query).to_rhai_error()?;
|
||||||
|
|
||||||
|
// Convert Vec<String> to Rhai Array
|
||||||
|
let mut array = Array::new();
|
||||||
|
for package in packages {
|
||||||
|
array.push(package.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for os::package::PackHero::is_installed
|
||||||
|
///
|
||||||
|
/// Check if a package is installed using the system package manager.
|
||||||
|
pub fn package_is_installed(package: &str) -> Result<bool, Box<EvalAltResult>> {
|
||||||
|
let hero = PackHero::new();
|
||||||
|
hero.is_installed(package).to_rhai_error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread-local storage for package debug flag
|
||||||
|
thread_local! {
|
||||||
|
static PACKAGE_DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the debug mode for package management operations
|
||||||
|
pub fn package_set_debug(debug: bool) -> bool {
|
||||||
|
let mut hero = PackHero::new();
|
||||||
|
hero.set_debug(debug);
|
||||||
|
|
||||||
|
// Also set the thread-local debug flag
|
||||||
|
PACKAGE_DEBUG.with(|cell| {
|
||||||
|
*cell.borrow_mut() = debug;
|
||||||
|
});
|
||||||
|
|
||||||
|
debug
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current platform name for package management
|
||||||
|
pub fn package_platform() -> String {
|
||||||
|
let hero = PackHero::new();
|
||||||
|
match hero.platform() {
|
||||||
|
os::package::Platform::Ubuntu => "Ubuntu".to_string(),
|
||||||
|
os::package::Platform::MacOS => "MacOS".to_string(),
|
||||||
|
os::package::Platform::Unknown => "Unknown".to_string(),
|
||||||
|
}
|
||||||
|
}
|
210
src/rhaiexamples/containerd_grpc_setup.rhai
Normal file
210
src/rhaiexamples/containerd_grpc_setup.rhai
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
// containerd_grpc_setup.rhai
|
||||||
|
//
|
||||||
|
// This script sets up a Rust project with gRPC connectivity to containerd
|
||||||
|
// Following the steps from the instructions document
|
||||||
|
|
||||||
|
|
||||||
|
run("apt-get -y protobuf-compiler ");
|
||||||
|
|
||||||
|
// Step 1: Set up project directory
|
||||||
|
let project_dir = "/tmp/containerd-rust-client";
|
||||||
|
print(`Setting up project in: ${project_dir}`);
|
||||||
|
|
||||||
|
// Clean up any existing directory
|
||||||
|
if exist(project_dir) {
|
||||||
|
print("Found existing project directory, removing it...");
|
||||||
|
delete(project_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our project directory
|
||||||
|
mkdir(project_dir);
|
||||||
|
|
||||||
|
// Change to the project directory
|
||||||
|
chdir(project_dir);
|
||||||
|
|
||||||
|
// Step 2: Clone containerd's gRPC proto files
|
||||||
|
print("Cloning containerd repository to get proto files...");
|
||||||
|
let git_tree = gittree_new(project_dir);
|
||||||
|
let repos = git_tree.get("https://github.com/containerd/containerd.git");
|
||||||
|
let repo = repos[0];
|
||||||
|
print(`Cloned containerd repository to: ${repo.path()}`);
|
||||||
|
|
||||||
|
// Step 3: Create necessary project files
|
||||||
|
print("Creating Cargo.toml file...");
|
||||||
|
// Using raw string with # for multiline content
|
||||||
|
let cargo_toml = #"
|
||||||
|
[package]
|
||||||
|
name = "containerd-rust-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tonic = "0.11"
|
||||||
|
prost = "0.12"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
hyper-unix-connector = "0.2.0"
|
||||||
|
tower = "0.4"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tonic-build = "0.11"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
file_write("Cargo.toml", cargo_toml);
|
||||||
|
print("Created Cargo.toml file");
|
||||||
|
|
||||||
|
// Step 4: Set up build.rs to compile protos
|
||||||
|
print("Creating build.rs file...");
|
||||||
|
let build_rs = #"
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=containerd/api/services/images/v1/images.proto");
|
||||||
|
println!("cargo:rerun-if-changed=containerd/api/services/containers/v1/containers.proto");
|
||||||
|
|
||||||
|
tonic_build::configure()
|
||||||
|
.build_server(false)
|
||||||
|
.compile(
|
||||||
|
&[
|
||||||
|
"containerd/api/services/images/v1/images.proto",
|
||||||
|
"containerd/api/services/containers/v1/containers.proto",
|
||||||
|
// Add more proto files as needed
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
"containerd",
|
||||||
|
"containerd/api",
|
||||||
|
"containerd/api/types"
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
file_write("build.rs", build_rs);
|
||||||
|
print("Created build.rs file");
|
||||||
|
|
||||||
|
// Step 5: Create src directory and main.rs file
|
||||||
|
mkdir("src");
|
||||||
|
|
||||||
|
// Create a helper function for Unix socket connection
|
||||||
|
print("Creating src/main.rs file...");
|
||||||
|
let main_rs = #"
|
||||||
|
use tonic::transport::{Channel, Endpoint, Uri};
|
||||||
|
use tower::service_fn;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
// The proto-generated modules will be available after build
|
||||||
|
// use containerd::services::images::v1::{
|
||||||
|
// images_client::ImagesClient,
|
||||||
|
// GetImageRequest,
|
||||||
|
// };
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!("Connecting to containerd gRPC...");
|
||||||
|
|
||||||
|
// Path to containerd socket
|
||||||
|
let socket_path = "/run/containerd/containerd.sock";
|
||||||
|
|
||||||
|
// Connect to the Unix socket
|
||||||
|
let channel = unix_socket_channel(socket_path).await?;
|
||||||
|
|
||||||
|
// Now we'd create a client and use it
|
||||||
|
// let mut client = ImagesClient::new(channel);
|
||||||
|
// let response = client.get(GetImageRequest {
|
||||||
|
// name: "docker.io/library/ubuntu:latest".to_string(),
|
||||||
|
// }).await?;
|
||||||
|
// println!("Image: {:?}", response.into_inner());
|
||||||
|
|
||||||
|
println!("Connection to containerd socket established successfully!");
|
||||||
|
println!("This is a template - uncomment the client code after building.");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to connect to Unix socket
|
||||||
|
async fn unix_socket_channel(path: &str) -> Result<Channel, Box<dyn std::error::Error>> {
|
||||||
|
// Use a placeholder URI since Unix sockets don't have URIs
|
||||||
|
let endpoint = Endpoint::try_from("http://[::]:50051")?;
|
||||||
|
|
||||||
|
// The socket path to connect to
|
||||||
|
let path_to_connect = path.to_string();
|
||||||
|
|
||||||
|
// Create a connector function that connects to the Unix socket
|
||||||
|
let channel = endpoint
|
||||||
|
.connect_with_connector(service_fn(move |_: Uri| {
|
||||||
|
let path = path_to_connect.clone();
|
||||||
|
async move {
|
||||||
|
tokio::net::UnixStream::connect(path)
|
||||||
|
.await
|
||||||
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(channel)
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
file_write("src/main.rs", main_rs);
|
||||||
|
print("Created src/main.rs file");
|
||||||
|
|
||||||
|
// Step 6: Create a README.md file
|
||||||
|
print("Creating README.md file...");
|
||||||
|
// Using raw string with # for multiline content containing markdown backticks
|
||||||
|
let readme = #"# containerd Rust gRPC Client
|
||||||
|
|
||||||
|
A Rust client for interacting with containerd via gRPC.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Rust and Cargo installed
|
||||||
|
- containerd running on your system
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Connect to containerd via Unix socket
|
||||||
|
- Query image information
|
||||||
|
- Work with containers
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
- `src/main.rs` - Example client code
|
||||||
|
- `build.rs` - Proto compilation script
|
||||||
|
"#;
|
||||||
|
|
||||||
|
file_write("README.md", readme);
|
||||||
|
print("Created README.md file");
|
||||||
|
|
||||||
|
// Step 7: Build the project
|
||||||
|
print("Building the project...");
|
||||||
|
let build_result = run("cargo build");
|
||||||
|
|
||||||
|
if build_result.success {
|
||||||
|
print("Project built successfully!");
|
||||||
|
} else {
|
||||||
|
print(`Build failed with error: ${build_result.stderr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
print(`
|
||||||
|
--------------------------------------
|
||||||
|
🎉 Setup complete!
|
||||||
|
|
||||||
|
Project created at: ${project_dir}
|
||||||
|
|
||||||
|
To use the project:
|
||||||
|
1. cd ${project_dir}
|
||||||
|
2. cargo run
|
||||||
|
|
||||||
|
Note: Make sure containerd is running and the socket exists at /run/containerd/containerd.sock
|
||||||
|
--------------------------------------
|
||||||
|
`);
|
@ -49,7 +49,7 @@ print(` Extraction successful: ${success_msg}`);
|
|||||||
|
|
||||||
// Download binary file and check size
|
// Download binary file and check size
|
||||||
print("\nTesting binary download:");
|
print("\nTesting binary download:");
|
||||||
download(binary_url, binary_dest, 8000);
|
download_file(binary_url, binary_dest, 8000);
|
||||||
|
|
||||||
// Check file size using our new file_size function
|
// Check file size using our new file_size function
|
||||||
let size_bytes = file_size(binary_dest);
|
let size_bytes = file_size(binary_dest);
|
||||||
|
124
src/rhaiexamples/instructions_grpc.md
Normal file
124
src/rhaiexamples/instructions_grpc.md
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## ✅ Step-by-step: Connect Rust to containerd via gRPC
|
||||||
|
|
||||||
|
|
||||||
|
CREATE A rhai script executing the following instructions below
|
||||||
|
use all rhai script features we have + give priority to what functions we have defined in /src/docs/docs/sal in this repo
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. 🧱 Install Dependencies
|
||||||
|
|
||||||
|
Add this to your `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
tonic = "0.11"
|
||||||
|
prost = "0.12"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tonic-build = "0.11"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 📁 Clone containerd's gRPC proto files
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/containerd/containerd.git
|
||||||
|
cd containerd
|
||||||
|
```
|
||||||
|
|
||||||
|
Containerd's API protos are in:
|
||||||
|
```
|
||||||
|
api/services/ # gRPC service definitions
|
||||||
|
api/types/ # message types
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 📦 Set up `build.rs` to compile protos
|
||||||
|
|
||||||
|
In your Rust project root, create a `build.rs` file:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
tonic_build::configure()
|
||||||
|
.build_server(false)
|
||||||
|
.compile(
|
||||||
|
&[
|
||||||
|
"containerd/api/services/images/v1/images.proto",
|
||||||
|
"containerd/api/services/containers/v1/containers.proto",
|
||||||
|
// Add more proto files as needed
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
"containerd/api",
|
||||||
|
"containerd/api/types"
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to place the `containerd` directory somewhere your build can see — for example, symlink it or move it into your project as `proto/containerd`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 🧪 Example: Connect to containerd's image service
|
||||||
|
|
||||||
|
After `build.rs` compiles the protos, your code can access them like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tonic::transport::Channel;
|
||||||
|
use containerd::services::images::v1::{
|
||||||
|
images_client::ImagesClient,
|
||||||
|
GetImageRequest,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Connect to containerd's gRPC socket (default path)
|
||||||
|
let channel = Channel::from_static("http://[::]:50051") // placeholder
|
||||||
|
.connect()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut client = ImagesClient::new(channel);
|
||||||
|
|
||||||
|
let response = client.get(GetImageRequest {
|
||||||
|
name: "docker.io/library/ubuntu:latest".to_string(),
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
println!("Image: {:?}", response.into_inner());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
🔧 Note: containerd uses a **Unix socket**, so replace the channel connection with:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tonic::transport::{Endpoint, Uri};
|
||||||
|
use tower::service_fn;
|
||||||
|
use hyper_unix_connector::UnixConnector;
|
||||||
|
|
||||||
|
let uds = tokio::net::UnixStream::connect("/run/containerd/containerd.sock").await?;
|
||||||
|
let channel = Endpoint::try_from("http://[::]:50051")?
|
||||||
|
.connect_with_connector(service_fn(move |_| async move {
|
||||||
|
Ok::<_, std::io::Error>(uds)
|
||||||
|
}))
|
||||||
|
.await?;
|
||||||
|
```
|
||||||
|
|
||||||
|
(We can wrap that part into a helper if you want.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 🔁 Rebuild the project
|
||||||
|
|
||||||
|
Each time you add or change a `.proto`, rebuild to regenerate code:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo clean && cargo build
|
||||||
|
```
|
@ -2,25 +2,24 @@
|
|||||||
|
|
||||||
|
|
||||||
fn nerdctl_download(){
|
fn nerdctl_download(){
|
||||||
// let name="nerdctl";
|
let name="nerdctl";
|
||||||
// let url="https://github.com/containerd/nerdctl/releases/download/v2.0.4/nerdctl-2.0.4-linux-amd64.tar.gz";
|
let url="https://github.com/containerd/nerdctl/releases/download/v2.0.4/nerdctl-2.0.4-linux-amd64.tar.gz";
|
||||||
// download(url,`/tmp/${name}`,20000);
|
download(url,`/tmp/${name}`,20000);
|
||||||
// copy(`/tmp/${name}/*`,"/root/hero/bin/");
|
copy(`/tmp/${name}/*`,"/root/hero/bin/");
|
||||||
// delete(`/tmp/${name}`);
|
delete(`/tmp/${name}`);
|
||||||
|
|
||||||
// let name="containerd";
|
let name="containerd";
|
||||||
// let url="https://github.com/containerd/containerd/releases/download/v2.0.4/containerd-2.0.4-linux-amd64.tar.gz";
|
let url="https://github.com/containerd/containerd/releases/download/v2.0.4/containerd-2.0.4-linux-amd64.tar.gz";
|
||||||
// download(url,`/tmp/${name}`,20000);
|
download(url,`/tmp/${name}`,20000);
|
||||||
// copy(`/tmp/${name}/bin/*`,"/root/hero/bin/");
|
copy(`/tmp/${name}/bin/*`,"/root/hero/bin/");
|
||||||
// delete(`/tmp/${name}`);
|
delete(`/tmp/${name}`);
|
||||||
|
|
||||||
// run("apt-get -y install buildah runc")
|
run("apt-get -y install buildah runc");
|
||||||
|
|
||||||
let url="https://github.com/threefoldtech/rfs/releases/download/v2.0.6/rfs";
|
let url="https://github.com/threefoldtech/rfs/releases/download/v2.0.6/rfs";
|
||||||
download_file(url,`/tmp/rfs`,10000);
|
download_file(url,`/tmp/rfs`,10000);
|
||||||
chmod_exec(url);
|
chmod_exec("/tmp/rfs");
|
||||||
copy(`/tmp/rfs`,"/root/hero/bin/");
|
mv(`/tmp/rfs`,"/root/hero/bin/");
|
||||||
//delete(`/tmp/rfs`);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
113
src/rhaiexamples/package_management.rhai
Normal file
113
src/rhaiexamples/package_management.rhai
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// Example script demonstrating the package management functions
|
||||||
|
|
||||||
|
// Set debug mode to true to see detailed output
|
||||||
|
package_set_debug(true);
|
||||||
|
|
||||||
|
// Function to demonstrate package management on Ubuntu
|
||||||
|
fn demo_ubuntu() {
|
||||||
|
print("Demonstrating package management on Ubuntu...");
|
||||||
|
|
||||||
|
// Update package lists
|
||||||
|
print("Updating package lists...");
|
||||||
|
let result = package_update();
|
||||||
|
print(`Update result: ${result}`);
|
||||||
|
|
||||||
|
// Check if a package is installed
|
||||||
|
let package = "htop";
|
||||||
|
print(`Checking if ${package} is installed...`);
|
||||||
|
let is_installed = package_is_installed(package);
|
||||||
|
print(`${package} is installed: ${is_installed}`);
|
||||||
|
|
||||||
|
// Install a package if not already installed
|
||||||
|
if !is_installed {
|
||||||
|
print(`Installing ${package}...`);
|
||||||
|
let install_result = package_install(package);
|
||||||
|
print(`Install result: ${install_result}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// List installed packages (limited to first 5 for brevity)
|
||||||
|
print("Listing installed packages (first 5)...");
|
||||||
|
let packages = package_list();
|
||||||
|
for i in 0..min(5, packages.len()) {
|
||||||
|
print(` - ${packages[i]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for packages
|
||||||
|
let search_term = "editor";
|
||||||
|
print(`Searching for packages with term '${search_term}'...`);
|
||||||
|
let search_results = package_search(search_term);
|
||||||
|
print(`Found ${search_results.len()} packages. First 5 results:`);
|
||||||
|
for i in 0..min(5, search_results.len()) {
|
||||||
|
print(` - ${search_results[i]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the package if we installed it
|
||||||
|
if !is_installed {
|
||||||
|
print(`Removing ${package}...`);
|
||||||
|
let remove_result = package_remove(package);
|
||||||
|
print(`Remove result: ${remove_result}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to demonstrate package management on macOS
|
||||||
|
fn demo_macos() {
|
||||||
|
print("Demonstrating package management on macOS...");
|
||||||
|
|
||||||
|
// Update package lists
|
||||||
|
print("Updating package lists...");
|
||||||
|
let result = package_update();
|
||||||
|
print(`Update result: ${result}`);
|
||||||
|
|
||||||
|
// Check if a package is installed
|
||||||
|
let package = "wget";
|
||||||
|
print(`Checking if ${package} is installed...`);
|
||||||
|
let is_installed = package_is_installed(package);
|
||||||
|
print(`${package} is installed: ${is_installed}`);
|
||||||
|
|
||||||
|
// Install a package if not already installed
|
||||||
|
if !is_installed {
|
||||||
|
print(`Installing ${package}...`);
|
||||||
|
let install_result = package_install(package);
|
||||||
|
print(`Install result: ${install_result}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// List installed packages (limited to first 5 for brevity)
|
||||||
|
print("Listing installed packages (first 5)...");
|
||||||
|
let packages = package_list();
|
||||||
|
for i in 0..min(5, packages.len()) {
|
||||||
|
print(` - ${packages[i]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for packages
|
||||||
|
let search_term = "editor";
|
||||||
|
print(`Searching for packages with term '${search_term}'...`);
|
||||||
|
let search_results = package_search(search_term);
|
||||||
|
print(`Found ${search_results.len()} packages. First 5 results:`);
|
||||||
|
for i in 0..min(5, search_results.len()) {
|
||||||
|
print(` - ${search_results[i]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the package if we installed it
|
||||||
|
if !is_installed {
|
||||||
|
print(`Removing ${package}...`);
|
||||||
|
let remove_result = package_remove(package);
|
||||||
|
print(`Remove result: ${remove_result}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect platform and run the appropriate demo
|
||||||
|
fn main() {
|
||||||
|
// Create a PackHero instance to detect the platform
|
||||||
|
let platform = package_platform();
|
||||||
|
|
||||||
|
if platform == "Ubuntu" {
|
||||||
|
demo_ubuntu();
|
||||||
|
} else if platform == "MacOS" {
|
||||||
|
demo_macos();
|
||||||
|
} else {
|
||||||
|
print(`Unsupported platform: ${platform}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the main function
|
||||||
|
main();
|
@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read, Seek, SeekFrom};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
/// Represents the type of replacement to perform.
|
/// Represents the type of replacement to perform.
|
||||||
|
Loading…
Reference in New Issue
Block a user