provisioning-core/nulib/platform/vm/ssh_utils.nu

211 lines
5.7 KiB
Text
Raw Normal View History

# VM SSH Connectivity and Provisioning
#
# SSH operations for VMs: connection, provisioning, file transfer.
# Rule 1: Single purpose, Rule 2: Explicit types
# Selective imports (ADR-025 Phase 3 Layer 2).
use platform/vm/backend_libvirt.nu [libvirt-get-vm-ip]
use platform/vm/persistence.nu [get-vm-state]
export def "vm-ssh" [
vm_name: string # VM name
--command: string # Command to execute (optional)
]: record {
"""
SSH into VM or execute command.
Handles VM IP discovery and SSH connectivity.
Examples:
# SSH shell
provisioning vm ssh dev-rust
# Execute command
provisioning vm ssh dev-rust --command "uname -a"
"""
# Get VM IP
let ip = (get-vm-ip $vm_name)
if ($ip | is-empty) {
return {
success: false
error: $"Cannot determine IP for VM ($vm_name)"
}
}
# Wait for SSH if needed
let ssh_ready = (wait-for-ssh $ip)
if not $ssh_ready {
return {
success: false
error: $"SSH not responding on ($ip)"
}
}
# Execute SSH
if ($command | is-empty) {
# Interactive shell
bash -c $"ssh -o StrictHostKeyChecking=no root@($ip)"
{success: true}
} else {
# Execute command (no try-catch)
let output = (do { bash -c $"ssh -o StrictHostKeyChecking=no root@($ip) '($command)'" } | complete)
{
success: ($output.exit_code == 0)
output: $output.stdout
error: $output.stderr
}
}
}
export def "vm-scp-to" [
vm_name: string # VM name
local_path: string # Local file path
remote_path: string # Remote file path
]: record {
"""Copy file from host to VM"""
let ip = (get-vm-ip $vm_name)
if ($ip | is-empty) {
return {success: false, error: $"Cannot determine IP for VM ($vm_name)"}
}
let wait = (wait-for-ssh $ip)
if not $wait {
return {success: false, error: $"SSH not ready on ($ip)"}
}
# Copy file via SCP (no try-catch)
let result = (do { bash -c $"scp -r -o StrictHostKeyChecking=no ($local_path) root@($ip):($remote_path)" } | complete)
{
success: ($result.exit_code == 0)
message: $"Copied ($local_path) to ($ip):($remote_path)"
error: (if $result.exit_code != 0 { $result.stderr } else { "" })
}
}
export def "vm-scp-from" [
vm_name: string # VM name
remote_path: string # Remote file path
local_path: string # Local file path
]: record {
"""Copy file from VM to host"""
let ip = (get-vm-ip $vm_name)
if ($ip | is-empty) {
return {success: false, error: $"Cannot determine IP for VM ($vm_name)"}
}
let wait = (wait-for-ssh $ip)
if not $wait {
return {success: false, error: $"SSH not ready on ($ip)"}
}
# Copy file via SCP (no try-catch)
let result = (do { bash -c $"scp -r -o StrictHostKeyChecking=no root@($ip):($remote_path) ($local_path)" } | complete)
{
success: ($result.exit_code == 0)
message: $"Copied ($ip):($remote_path) to ($local_path)"
error: (if $result.exit_code != 0 { $result.stderr } else { "" })
}
}
export def "vm-exec" [
vm_name: string # VM name
command: string # Command to execute
]: record {
"""Execute command in VM and return output"""
if ($command | is-empty) {
return {success: false, error: "Command required"}
}
vm-ssh $vm_name --command=$command
}
def get-vm-ip [vm_name: string]: string {
"""Get VM IP address with caching"""
# Try to get from libvirt first (fast)
let libvirt_ip = (libvirt-get-vm-ip $vm_name)
if ($libvirt_ip | length) > 0 {
return $libvirt_ip
}
# Fallback: query state
let state = (get-vm-state $vm_name)
$state.ip_address // ""
}
def wait-for-ssh [ip: string, --timeout: int = 300]: bool {
"""Wait for SSH to become available"""
let start_time = (date now)
let timeout_duration = ($timeout)sec
mut attempts = 0
let max_attempts = ($timeout / 2) + 1 # Safety limit based on sleep 2sec
while { $attempts < $max_attempts } {
let elapsed = ((date now) - $start_time)
if $elapsed >= $timeout_duration {
return false
}
# Check SSH availability (no try-catch)
let ssh_check = (do { bash -c $"ssh-keyscan -t rsa ($ip) 2>/dev/null" } | complete)
if $ssh_check.exit_code == 0 {
return true
}
# Wait before retry
sleep 2sec
$attempts += 1
}
false
}
export def "vm-provision" [
vm_name: string # VM name
script: string # Script to execute
]: record {
"""
Provision VM with script.
Uploads and executes provisioning script.
"""
# Write script to temp file
let temp_script = $"/tmp/provision-($vm_name)-($env.RANDOM).sh"
# Create script file (no try-catch)
let create_result = (do { bash -c $"cat > ($temp_script) << 'SCRIPT'\n($script)\nSCRIPT" } | complete)
if $create_result.exit_code != 0 {
return {success: false, error: $"Failed to create script: ($create_result.stderr)"}
}
# SCP script to VM
let scp_result = (vm-scp-to $vm_name $temp_script "/tmp/provision.sh")
if not $scp_result.success {
bash -c $"rm -f ($temp_script)"
return $scp_result
}
# Execute script
let exec_result = (vm-ssh $vm_name --command "bash /tmp/provision.sh")
# Cleanup
bash -c $"rm -f ($temp_script)"
vm-ssh $vm_name --command "rm -f /tmp/provision.sh" | ignore
{
success: $exec_result.success
output: $exec_result.output
error: $exec_result.error
}
}