194 lines
5.3 KiB
Plaintext
194 lines
5.3 KiB
Plaintext
# VM State Persistence
|
|
#
|
|
# File-based VM state tracking. Stores metadata in JSON files.
|
|
# Rule 1: Single purpose, Rule 4: Pure functions, Rule 10: Stream large data
|
|
|
|
export def "record-vm-creation" [
|
|
vm_config: record # VM configuration
|
|
]: record {
|
|
"""Record a new VM in state tracking"""
|
|
|
|
let state_dir = (get-vm-state-dir)
|
|
bash -c $"mkdir -p ($state_dir)"
|
|
|
|
let state_file = $"($state_dir)/($vm_config.name).json"
|
|
|
|
let state = {
|
|
name: $vm_config.name
|
|
state: "stopped"
|
|
permanent: ($vm_config.permanent // false)
|
|
temporary: ($vm_config.temporary // false)
|
|
auto_cleanup: ($vm_config.auto_cleanup // false)
|
|
auto_cleanup_hours: ($vm_config.auto_cleanup_hours // 24)
|
|
cpu: $vm_config.cpu
|
|
memory_mb: $vm_config.memory_mb
|
|
disk_gb: $vm_config.disk_gb
|
|
base_image: $vm_config.base_image
|
|
backend: ($vm_config.backend // "libvirt")
|
|
taskservs: ($vm_config.taskservs // [])
|
|
created_at: (now | format date "%Y-%m-%dT%H:%M:%SZ" | debug)
|
|
ip_address: ""
|
|
mac_address: ""
|
|
}
|
|
|
|
# Save state (no try-catch)
|
|
let save_result = (do { bash -c $"cat > ($state_file) << 'EOF'\n($state | to json)\nEOF" } | complete)
|
|
if $save_result.exit_code != 0 {
|
|
return {success: false, error: $"Failed to record VM creation: ($save_result.stderr)"}
|
|
}
|
|
|
|
{success: true}
|
|
}
|
|
|
|
export def "get-vm-state" [
|
|
vm_name: string # VM name
|
|
]: record {
|
|
"""Get VM state from file"""
|
|
|
|
let state_dir = (get-vm-state-dir)
|
|
let state_file = $"($state_dir)/($vm_name).json"
|
|
|
|
# Guard: Check if state file can be opened and parsed as JSON (no try-catch)
|
|
let json_result = (do { open $state_file | from json } | complete)
|
|
if $json_result.exit_code == 0 {
|
|
$json_result.stdout
|
|
} else {
|
|
{}
|
|
}
|
|
}
|
|
|
|
export def "update-vm-state" [
|
|
vm_name: string # VM name
|
|
new_state: string # New state (running, stopped, etc.)
|
|
]: record {
|
|
"""Update VM state"""
|
|
|
|
let current = (get-vm-state $vm_name)
|
|
|
|
if ($current | is-empty) {
|
|
return {success: false, error: "VM not found"}
|
|
}
|
|
|
|
let updated = (
|
|
$current
|
|
| upsert state $new_state
|
|
| upsert last_action (now | format date "%Y-%m-%dT%H:%M:%SZ" | debug)
|
|
)
|
|
|
|
let state_dir = (get-vm-state-dir)
|
|
let state_file = $"($state_dir)/($vm_name).json"
|
|
|
|
# Update state (no try-catch)
|
|
let update_result = (do { bash -c $"cat > ($state_file) << 'EOF'\n($updated | to json)\nEOF" } | complete)
|
|
if $update_result.exit_code != 0 {
|
|
return {success: false, error: $"Failed to update VM state: ($update_result.stderr)"}
|
|
}
|
|
|
|
{success: true}
|
|
}
|
|
|
|
export def "remove-vm-state" [
|
|
vm_name: string # VM name
|
|
]: record {
|
|
"""Remove VM from state tracking"""
|
|
|
|
let state_dir = (get-vm-state-dir)
|
|
let state_file = $"($state_dir)/($vm_name).json"
|
|
|
|
# Remove state file (no try-catch)
|
|
let rm_result = (do { bash -c $"rm -f ($state_file)" } | complete)
|
|
if $rm_result.exit_code != 0 {
|
|
return {success: false, error: $"Failed to remove VM state: ($rm_result.stderr)"}
|
|
}
|
|
|
|
{success: true}
|
|
}
|
|
|
|
export def "list-all-vms" []: table {
|
|
"""List all VMs from state tracking"""
|
|
|
|
let state_dir = (get-vm-state-dir)
|
|
|
|
if not ($state_dir | path exists) {
|
|
return []
|
|
}
|
|
|
|
# List state files (no try-catch)
|
|
let ls_result = (do { bash -c $"ls -1 ($state_dir)/*.json 2>/dev/null" } | complete)
|
|
if $ls_result.exit_code != 0 {
|
|
return []
|
|
}
|
|
|
|
$ls_result.stdout
|
|
| lines
|
|
| where {|f| ($f | length) > 0}
|
|
| each {|f|
|
|
# Guard: Check if file can be opened and parsed as JSON (no try-catch)
|
|
let json_result = (do { open $f | from json } | complete)
|
|
if $json_result.exit_code == 0 {
|
|
$json_result.stdout
|
|
} else {
|
|
null
|
|
}
|
|
}
|
|
| compact
|
|
| where {|v| ($v | length) > 0}
|
|
}
|
|
|
|
def get-vm-state-dir []: string {
|
|
"""Get VM state directory"""
|
|
$"{{paths.workspace}}/vms/state"
|
|
}
|
|
|
|
export def "get-vm-stats" []: record {
|
|
"""Get VM statistics"""
|
|
|
|
let all_vms = (list-all-vms)
|
|
|
|
let permanent = ($all_vms | where permanent == true | length)
|
|
let temporary = ($all_vms | where temporary == true | length)
|
|
let total = ($all_vms | length)
|
|
|
|
{
|
|
total_vms: $total
|
|
permanent_vms: $permanent
|
|
temporary_vms: $temporary
|
|
running_vms: ($all_vms | where state == "running" | length)
|
|
stopped_vms: ($all_vms | where state == "stopped" | length)
|
|
}
|
|
}
|
|
|
|
export def "cleanup-temporary-vms" [
|
|
--older-than: int = 24 # Hours
|
|
]: record {
|
|
"""Cleanup temporary VMs older than specified hours"""
|
|
|
|
let all_vms = (list-all-vms)
|
|
let now = (now | format date "%Y-%m-%dT%H:%M:%SZ" | debug)
|
|
|
|
let to_cleanup = (
|
|
$all_vms
|
|
| filter {|vm|
|
|
($vm.temporary // false) and \
|
|
($vm.auto_cleanup // false)
|
|
}
|
|
)
|
|
|
|
let results = (
|
|
$to_cleanup
|
|
| map {|vm|
|
|
{
|
|
name: $vm.name
|
|
result: "skipped" # Placeholder for Phase 2
|
|
}
|
|
}
|
|
)
|
|
|
|
{
|
|
total_checked: ($to_cleanup | length)
|
|
cleaned_up: 0
|
|
skipped: ($to_cleanup | length)
|
|
results: $results
|
|
}
|
|
}
|