diff --git a/examples/nerdctl.rs b/examples/nerdctl.rs index 15a8833..b9757df 100644 --- a/examples/nerdctl.rs +++ b/examples/nerdctl.rs @@ -17,7 +17,8 @@ pub fn run_nerdctl_example() -> Result<(), NerdctlError> { // Step 2: Create a container from the image println!("\n=== Creating container from nginx:latest ==="); - let run_result = nerdctl::run("nginx:latest", Some("my-nginx"), true, Some(&["8080:80"]))?; + // Use "native" snapshotter to avoid overlay mount issues + let run_result = nerdctl::run("nginx:latest", Some("my-nginx"), true, Some(&["8080:80"]), Some("native"))?; println!("Container created: {}", run_result.stdout.trim()); let container_id = "my-nginx"; // Using the name we specified @@ -47,7 +48,8 @@ pub fn run_nerdctl_example() -> Result<(), NerdctlError> { // Step 7: Create a new container from our custom image println!("\n=== Creating container from custom image ==="); - nerdctl::run(image_name, Some("nginx-custom"), true, Some(&["8081:80"]))?; + // Use "native" snapshotter to avoid overlay mount issues + nerdctl::run(image_name, Some("nginx-custom"), true, Some(&["8081:80"]), Some("native"))?; println!("Custom container created"); // Step 8: List images @@ -78,5 +80,5 @@ pub fn run_all_examples() -> Result<(), NerdctlError> { } fn main() { - run_all_examples(); + let _ = run_all_examples().unwrap(); } \ No newline at end of file diff --git a/src/virt/nerdctl/cmd.rs b/src/virt/nerdctl/cmd.rs new file mode 100644 index 0000000..088f28d --- /dev/null +++ b/src/virt/nerdctl/cmd.rs @@ -0,0 +1,37 @@ +// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/cmd.rs + +// Basic nerdctl operations for container management +use std::process::Command; +use crate::process::CommandResult; +use super::NerdctlError; + +/// Execute a nerdctl command and return the result +pub fn execute_nerdctl_command(args: &[&str]) -> Result { + let output = Command::new("nerdctl") + .args(args) + .output(); + + 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), + }; + + if result.success { + Ok(result) + } else { + Err(NerdctlError::CommandFailed(format!("Command failed with code {}: {}", + result.code, result.stderr.trim()))) + } + }, + Err(e) => { + Err(NerdctlError::CommandExecutionFailed(e)) + } + } +} \ No newline at end of file diff --git a/src/virt/nerdctl/containers.rs b/src/virt/nerdctl/containers.rs new file mode 100644 index 0000000..273d0b2 --- /dev/null +++ b/src/virt/nerdctl/containers.rs @@ -0,0 +1,105 @@ +// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/containers.rs + +use crate::virt::nerdctl::execute_nerdctl_command; +use crate::process::CommandResult; +use super::NerdctlError; + +/// Run a container from an image +/// +/// # Arguments +/// +/// * `image` - The image to run +/// * `name` - Optional container name +/// * `detach` - Whether to run in detached mode +/// * `ports` - Optional port mappings (e.g., ["8080:80"]) +/// * `snapshotter` - Optional snapshotter to use (e.g., "native", "fuse-overlayfs") +pub fn run(image: &str, name: Option<&str>, detach: bool, ports: Option<&[&str]>, snapshotter: Option<&str>) -> Result { + // First, try to remove any existing container with the same name to avoid conflicts + if let Some(name_str) = name { + // Ignore errors since the container might not exist + let _ = execute_nerdctl_command(&["rm", "-f", name_str]); + } + + let mut args = vec!["run"]; + + if detach { + args.push("-d"); + } + + if let Some(name_str) = name { + args.push("--name"); + args.push(name_str); + } + + if let Some(port_mappings) = ports { + for port in port_mappings { + args.push("-p"); + args.push(port); + } + } + + if let Some(snapshotter_value) = snapshotter { + args.push("--snapshotter"); + args.push(snapshotter_value); + } + + // Add flags to avoid BPF issues + args.push("--cgroup-manager=cgroupfs"); + + args.push(image); + + execute_nerdctl_command(&args) +} + +/// Execute a command in a container +/// +/// # Arguments +/// +/// * `container` - The container ID or name +/// * [command](cci:1://file:///root/code/git.ourworld.tf/herocode/sal/src/process/run.rs:303:0-324:1) - The command to run +pub fn exec(container: &str, command: &str) -> Result { + execute_nerdctl_command(&["exec", container, "sh", "-c", command]) +} + +/// Copy files between container and local filesystem +/// +/// # Arguments +/// +/// * [source](cci:1://file:///root/code/git.ourworld.tf/herocode/sal/src/process/run.rs:55:4-64:5) - Source path (can be container:path or local path) +/// * `dest` - Destination path (can be container:path or local path) +pub fn copy(source: &str, dest: &str) -> Result { + execute_nerdctl_command(&["cp", source, dest]) +} + +/// Stop a container +/// +/// # Arguments +/// +/// * `container` - The container ID or name +pub fn stop(container: &str) -> Result { + execute_nerdctl_command(&["stop", container]) +} + +/// Remove a container +/// +/// # Arguments +/// +/// * `container` - The container ID or name +pub fn remove(container: &str) -> Result { + execute_nerdctl_command(&["rm", container]) +} + +/// List containers +/// +/// # Arguments +/// +/// * `all` - Whether to show all containers (including stopped ones) +pub fn list(all: bool) -> Result { + let mut args = vec!["ps"]; + + if all { + args.push("-a"); + } + + execute_nerdctl_command(&args) +} \ No newline at end of file diff --git a/src/virt/nerdctl/images.rs b/src/virt/nerdctl/images.rs new file mode 100644 index 0000000..11f51db --- /dev/null +++ b/src/virt/nerdctl/images.rs @@ -0,0 +1,86 @@ +// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/images.rs + +use std::collections::HashMap; +use crate::virt::nerdctl::execute_nerdctl_command; +use crate::process::CommandResult; +use super::NerdctlError; +use serde_json::{self, Value}; +use serde::{Deserialize, Serialize}; + +/// Represents a container image +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Image { + /// Image ID + pub id: String, + /// Image repository + pub repository: String, + /// Image tag + pub tag: String, + /// Image size + pub size: String, + /// Creation timestamp + pub created: String, +} + +/// List images in local storage +pub fn images() -> Result { + execute_nerdctl_command(&["images"]) +} + +/// Remove one or more images +/// +/// # Arguments +/// +/// * `image` - Image ID or name +pub fn image_remove(image: &str) -> Result { + execute_nerdctl_command(&["rmi", image]) +} + +/// Push an image to a registry +/// +/// # Arguments +/// +/// * `image` - Image name +/// * `destination` - Destination registry URL +pub fn image_push(image: &str, destination: &str) -> Result { + execute_nerdctl_command(&["push", image, destination]) +} + +/// Add an additional name to a local image +/// +/// # Arguments +/// +/// * `image` - Image ID or name +/// * `new_name` - New name for the image +pub fn image_tag(image: &str, new_name: &str) -> Result { + execute_nerdctl_command(&["tag", image, new_name]) +} + +/// Pull an image from a registry +/// +/// # Arguments +/// +/// * `image` - Image name +pub fn image_pull(image: &str) -> Result { + execute_nerdctl_command(&["pull", image]) +} + +/// Commit a container to an image +/// +/// # Arguments +/// +/// * `container` - Container ID or name +/// * `image_name` - New name for the image +pub fn image_commit(container: &str, image_name: &str) -> Result { + execute_nerdctl_command(&["commit", container, image_name]) +} + +/// Build an image using a Dockerfile +/// +/// # Arguments +/// +/// * `tag` - Tag for the new image +/// * `context_path` - Path to the build context +pub fn image_build(tag: &str, context_path: &str) -> Result { + execute_nerdctl_command(&["build", "-t", tag, context_path]) +} \ No newline at end of file diff --git a/src/virt/nerdctl/mod.rs b/src/virt/nerdctl/mod.rs new file mode 100644 index 0000000..fad385f --- /dev/null +++ b/src/virt/nerdctl/mod.rs @@ -0,0 +1,47 @@ +mod containers; +mod images; +mod cmd; + +use std::fmt; +use std::error::Error; +use std::io; + +/// Error type for nerdctl operations +#[derive(Debug)] +pub enum NerdctlError { + /// The nerdctl command failed to execute + CommandExecutionFailed(io::Error), + /// The nerdctl 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 NerdctlError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + NerdctlError::CommandExecutionFailed(e) => write!(f, "Failed to execute nerdctl command: {}", e), + NerdctlError::CommandFailed(e) => write!(f, "Nerdctl command failed: {}", e), + NerdctlError::JsonParseError(e) => write!(f, "Failed to parse JSON: {}", e), + NerdctlError::ConversionError(e) => write!(f, "Conversion error: {}", e), + NerdctlError::Other(e) => write!(f, "{}", e), + } + } +} + +impl Error for NerdctlError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + NerdctlError::CommandExecutionFailed(e) => Some(e), + _ => None, + } + } +} + +pub use containers::*; +pub use images::*; +pub use cmd::*; \ No newline at end of file diff --git a/src/virt/nerdctl/nerdctl-essentials.md b/src/virt/nerdctl/nerdctl-essentials.md index 1ca2251..809c02f 100644 --- a/src/virt/nerdctl/nerdctl-essentials.md +++ b/src/virt/nerdctl/nerdctl-essentials.md @@ -465,3 +465,39 @@ nerdctl supports various security features: - `--security-opt apparmor=profile`: Apply an AppArmor profile - `--cap-add`/`--cap-drop`: Add or drop Linux capabilities - `--privileged`: Give extended privileges to the container + +## Typical Workflow Example + +```bash +# Create a container from an existing image +container=$(nerdctl run -d --name my-nginx nginx:latest) + +# Execute a command in the container +nerdctl exec $container apt-get update +nerdctl exec $container apt-get install -y curl + +# Copy local configuration files to the container +nerdctl cp ./nginx.conf $container:/etc/nginx/nginx.conf + +# Commit the container to create a new image +nerdctl commit $container my-custom-nginx:latest + +# Stop and remove the container +nerdctl stop $container +nerdctl rm $container + +# Create a new container from our custom image +nerdctl run -d --name nginx-custom -p 8080:80 my-custom-nginx:latest + +# Build an image using a Dockerfile +nerdctl build -t my-app:latest . + +# Push the image to a registry +nerdctl push my-custom-nginx:latest docker.io/username/my-custom-nginx:latest + +# List images +nerdctl images + +# List containers +nerdctl ps -a +``` \ No newline at end of file