175 lines
4.9 KiB
Text
175 lines
4.9 KiB
Text
# Runtime abstraction module for Docker, Podman, OrbStack, Colima, nerdctl
|
|
#
|
|
# Provides unified interface for container runtime operations across platforms.
|
|
# Follows NUSHELL_GUIDELINES.md: single purpose, explicit types, early return, atomic operations
|
|
|
|
# Detect available container runtime on the system
|
|
#
|
|
# Returns: record with runtime info
|
|
# Errors: propagates if no runtime found
|
|
export def runtime-detect [] {
|
|
let runtimes = [
|
|
{ name: "docker", command: "docker", priority: 1 }
|
|
{ name: "podman", command: "podman", priority: 2 }
|
|
{ name: "orbstack", command: "orbctl", priority: 3 }
|
|
{ name: "colima", command: "colima", priority: 4 }
|
|
{ name: "nerdctl", command: "nerdctl", priority: 5 }
|
|
]
|
|
|
|
let available = (
|
|
$runtimes
|
|
| each {|rt|
|
|
let exists = (^which $rt.command 2>/dev/null | length) > 0
|
|
{
|
|
name: $rt.name
|
|
command: $rt.command
|
|
priority: $rt.priority
|
|
available: $exists
|
|
}
|
|
}
|
|
| where available
|
|
| sort-by priority
|
|
)
|
|
|
|
if ($available | length) == 0 {
|
|
error "No container runtime detected. Install Docker, Podman, or another supported runtime."
|
|
}
|
|
|
|
$available | first
|
|
}
|
|
|
|
# Execute command in detected runtime
|
|
#
|
|
# Parameters:
|
|
# command: string - Command to execute
|
|
# --check: bool - Dry-run mode (no execution)
|
|
#
|
|
# Returns: string - Command output
|
|
# Errors: propagates from command execution
|
|
export def runtime-exec [command: string, --check = false] {
|
|
# Validate inputs early
|
|
if ($command | str trim) == "" {
|
|
error "Command cannot be empty"
|
|
}
|
|
|
|
let runtime = (runtime-detect)
|
|
let full_command = ($"($runtime.command) ($command)")
|
|
|
|
if $check {
|
|
return $"Would execute: [$full_command]"
|
|
}
|
|
|
|
# Execute atomically - succeed or fail completely
|
|
let result = (
|
|
do {
|
|
^sh -c $full_command
|
|
} | complete
|
|
)
|
|
|
|
if $result.exit_code == 0 {
|
|
$result.stdout
|
|
} else {
|
|
error $"Runtime execution failed: [$runtime.name]\nCommand: [$command]\nError: [$result.stderr]"
|
|
}
|
|
}
|
|
|
|
# Adapt docker-compose file for detected runtime
|
|
#
|
|
# Parameters:
|
|
# file_path: string - Path to compose file
|
|
#
|
|
# Returns: string - Compose command for this runtime
|
|
# Errors: propagates if file not found or runtime not available
|
|
export def runtime-compose [file_path: string] {
|
|
# Validate input early
|
|
if (not ($file_path | path exists)) {
|
|
error $"Compose file not found: [$file_path]"
|
|
}
|
|
|
|
let runtime = (runtime-detect)
|
|
|
|
# Generate appropriate compose command based on runtime
|
|
match $runtime.name {
|
|
"docker" => { $"docker compose -f [$file_path]" }
|
|
"podman" => { $"podman compose -f [$file_path]" }
|
|
"orbstack" | "colima" => { $"docker compose -f [$file_path]" }
|
|
"nerdctl" => { $"nerdctl compose -f [$file_path]" }
|
|
_ => { error $"Unknown runtime: [$runtime.name]" }
|
|
}
|
|
}
|
|
|
|
# Get runtime information
|
|
#
|
|
# Returns: record - Runtime details
|
|
# Errors: propagates if no runtime available
|
|
export def runtime-info [] {
|
|
let rt = (runtime-detect)
|
|
|
|
{
|
|
name: $rt.name
|
|
command: $rt.command
|
|
available: true
|
|
version: (
|
|
let result = (do {
|
|
let ver_output = (^sh -c $"($rt.command) --version" 2>&1)
|
|
$ver_output | str trim | str substring [0..<40]
|
|
} | complete)
|
|
|
|
if $result.exit_code != 0 {
|
|
"unknown"
|
|
} else {
|
|
$result.stdout
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
# List all available runtimes on the system
|
|
#
|
|
# Returns: table - All available runtimes
|
|
# Errors: none (returns empty if none available)
|
|
export def runtime-list [] {
|
|
let runtimes = [
|
|
{ name: "docker", command: "docker" }
|
|
{ name: "podman", command: "podman" }
|
|
{ name: "orbstack", command: "orbctl" }
|
|
{ name: "colima", command: "colima" }
|
|
{ name: "nerdctl", command: "nerdctl" }
|
|
]
|
|
|
|
$runtimes
|
|
| each {|rt|
|
|
let exists = (^which $rt.command 2>/dev/null | length) > 0
|
|
{
|
|
name: $rt.name
|
|
command: $rt.command
|
|
available: $exists
|
|
}
|
|
}
|
|
| where available
|
|
}
|
|
|
|
#[cfg(test)]
|
|
# Tests for runtime module
|
|
def test-runtime-detect [] {
|
|
# Note: Tests require runtime to be installed
|
|
let result = (do { runtime-detect } | complete)
|
|
let rt = if $result.exit_code != 0 { null } else { $result.stdout }
|
|
if ($rt != null) {
|
|
assert ($rt.name != "")
|
|
}
|
|
}
|
|
|
|
def test-runtime-info [] {
|
|
let result = (do { runtime-info } | complete)
|
|
let info = if $result.exit_code != 0 { null } else { $result.stdout }
|
|
if ($info != null) {
|
|
assert ($info.name != "")
|
|
}
|
|
}
|
|
|
|
def test-runtime-list [] {
|
|
let list = (runtime-list)
|
|
# Should return list of available runtimes (0 or more)
|
|
assert (($list | length) >= 0)
|
|
}
|