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

410 lines
12 KiB
Text

# Nested Provisioning System (Phase 4)
#
# Support for nested VMs (VM → VM → Containers).
# Rule 1: Single purpose, Rule 5: Atomic operations
# Selective imports (ADR-025 Phase 3 Layer 2).
use platform/vm/lifecycle.nu [vm-info]
use platform/vm/volume_management.nu [volume-attach volume-detach]
use platform/vm/network_management.nu [network-connect network-disconnect]
export def "nested-vm-create" [
name: string # VM name
parent_vm: string # Parent VM name
--cpu: int = 2 # CPU cores
--memory: int = 2048 # Memory in MB
--disk: int = 20 # Disk in GB
--networks: list<string> = [] # Networks to connect
--volumes: list<string> = [] # Volumes to attach
--auto-start = false # Auto-start on parent boot
--nested-virt = true # Enable nested virtualization
]: record {
"""
Create a nested VM inside another VM.
Enables multi-level virtualization for complex deployments.
"""
if ($name | is-empty) {
return {success: false, error: "VM name required"}
}
if ($parent_vm | is-empty) {
return {success: false, error: "Parent VM required"}
}
if ($cpu < 1 or $cpu > 32) {
return {success: false, error: "CPU must be 1-32"}
}
if ($memory < 512 or $memory > 65536) {
return {success: false, error: "Memory must be 512MB-64GB"}
}
# Verify parent VM exists
let parent_info = (vm-info $parent_vm)
if ("error" in $parent_info) {
return {success: false, error: "Parent VM not found"}
}
# Check nesting depth
let nesting_depth = (get-nesting-depth $parent_vm)
if $nesting_depth >= 3 {
return {success: false, error: "Maximum nesting depth (3) exceeded"}
}
let nested_dir = (get-nested-directory)
bash -c $"mkdir -p ($nested_dir)" | complete
let nested_meta = {
name: $name
parent_vm: $parent_vm
nesting_depth: ($nesting_depth + 1)
cpu: $cpu
memory_mb: $memory
disk_gb: $disk
networks: $networks
volumes: $volumes
auto_start: $auto_start
nested_virt: $nested_virt
created_at: (date now | format date "%Y-%m-%dT%H:%M:%SZ")
status: "created"
}
# Create VM disk (no try-catch)
let create_result = (do { bash -c $"qemu-img create -f qcow2 ($nested_dir)/($name).qcow2 ($disk)G" } | complete)
if $create_result.exit_code != 0 {
return {success: false, error: $"Failed to create VM disk: ($create_result.stderr)"}
}
# Save metadata (no try-catch)
let save_result = (do { bash -c $"cat > ($nested_dir)/($name).json << 'EOF'\n($nested_meta | to json)\nEOF" } | complete)
if $save_result.exit_code != 0 {
return {success: false, error: $"Failed to save metadata: ($save_result.stderr)"}
}
# Connect to networks (no try-catch)
$networks | each {|network|
network-connect $network $name
}
# Attach volumes (no try-catch)
$volumes | each {|volume|
volume-attach $volume $name
}
{
success: true
nested_vm: $name
parent_vm: $parent_vm
cpu: $cpu
memory_mb: $memory
disk_gb: $disk
nesting_depth: ($nesting_depth + 1)
}
}
export def "nested-vm-list" [
parent_vm: string = "" # Filter by parent (empty = all)
]: table {
"""
List nested VMs, optionally filtered by parent.
"""
let nested_dir = (get-nested-directory)
if (not ($nested_dir | path exists)) {
return []
}
bash -c $"ls -1 ($nested_dir)/*.json 2>/dev/null"
| lines
| each {|file|
# Guard: Check if file can be opened and parsed as JSON (no try-catch)
let json_result = (do { open $file | from json } | complete)
if $json_result.exit_code == 0 {
let meta = ($json_result.stdout)
if ($parent_vm | is-empty) or ($meta.parent_vm == $parent_vm) {
{
name: $meta.name
parent: $meta.parent_vm
depth: $meta.nesting_depth
cpu: $meta.cpu
memory_mb: $meta.memory_mb
disk_gb: $meta.disk_gb
status: $meta.status
created: $meta.created_at
}
}
} else {
null
}
}
| compact
}
export def "nested-vm-info" [
name: string # VM name
]: record {
"""
Get detailed nested VM information.
"""
let nested_dir = (get-nested-directory)
let meta_file = $"($nested_dir)/($name).json"
if (not ($meta_file | path exists)) {
return {success: false, error: "Nested VM not found"}
}
# Load metadata (no try-catch)
let meta_result = (do { open $meta_file | from json } | complete)
if $meta_result.exit_code != 0 {
return {success: false, error: $"Failed to load metadata: ($meta_result.stderr)"}
}
let meta = ($meta_result.stdout)
{
success: true
name: $meta.name
parent_vm: $meta.parent_vm
nesting_depth: $meta.nesting_depth
cpu: $meta.cpu
memory_mb: $meta.memory_mb
disk_gb: $meta.disk_gb
networks: $meta.networks
volumes: $meta.volumes
auto_start: $meta.auto_start
nested_virt: $meta.nested_virt
created: $meta.created_at
status: $meta.status
}
}
export def "nested-vm-delete" [
name: string # VM name
--force = false # Force delete
]: record {
"""
Delete a nested VM.
"""
let nested_dir = (get-nested-directory)
let meta_file = $"($nested_dir)/($name).json"
if (not ($meta_file | path exists)) {
return {success: false, error: "Nested VM not found"}
}
# Load metadata (no try-catch)
let meta_result = (do { open $meta_file | from json } | complete)
if $meta_result.exit_code != 0 {
return {success: false, error: $"Failed to load metadata: ($meta_result.stderr)"}
}
let meta = ($meta_result.stdout)
# Detach volumes and networks (no try-catch)
$meta.volumes | each {|volume|
volume-detach $volume $name
}
$meta.networks | each {|network|
network-disconnect $network $name
}
# Delete VM disk and metadata (no try-catch)
let rm_disk_result = (do { bash -c $"rm -f ($nested_dir)/($name).qcow2" } | complete)
if $rm_disk_result.exit_code != 0 {
return {success: false, error: $"Failed to delete VM disk: ($rm_disk_result.stderr)"}
}
let rm_meta_result = (do { bash -c $"rm -f ($meta_file)" } | complete)
if $rm_meta_result.exit_code != 0 {
return {success: false, error: $"Failed to delete metadata: ($rm_meta_result.stderr)"}
}
{
success: true
message: "Nested VM deleted"
}
}
export def "container-create" [
name: string # Container name
image: string # Container image
parent_vm: string # Parent VM name
--tag: string = "latest" # Image tag
--cpu-millicores: int = 1000 # CPU in millicores
--memory-mb: int = 512 # Memory in MB
--networks: list<string> = [] # Networks
--volumes: list<string> = [] # Volume mounts
--ports: list<string> = [] # Port mappings
--env: record = {} # Environment variables
--auto-start = false # Auto-start
]: record {
"""
Create a container inside a nested VM.
Supports Docker, Podman, and containerd runtimes.
"""
if ($name | is-empty) {
return {success: false, error: "Container name required"}
}
if ($image | is-empty) {
return {success: false, error: "Image required"}
}
let containers_dir = (get-containers-directory)
bash -c $"mkdir -p ($containers_dir)" | complete
let container_meta = {
name: $name
image: $"($image):($tag)"
parent_vm: $parent_vm
cpu_millicores: $cpu_millicores
memory_mb: $memory_mb
networks: $networks
volumes: $volumes
ports: $ports
environment: $env
auto_start: $auto_start
created_at: (date now | format date "%Y-%m-%dT%H:%M:%SZ")
status: "created"
}
# Save container metadata (no try-catch)
let save_result = (do { bash -c $"cat > ($containers_dir)/($name).json << 'EOF'\n($container_meta | to json)\nEOF" } | complete)
if $save_result.exit_code != 0 {
return {success: false, error: $"Failed to save container metadata: ($save_result.stderr)"}
}
{
success: true
container: $name
image: $container_meta.image
parent_vm: $parent_vm
cpu_millicores: $cpu_millicores
memory_mb: $memory_mb
}
}
export def "container-list" [
parent_vm: string = "" # Filter by parent VM
]: table {
"""
List containers, optionally filtered by parent VM.
"""
let containers_dir = (get-containers-directory)
if (not ($containers_dir | path exists)) {
return []
}
bash -c $"ls -1 ($containers_dir)/*.json 2>/dev/null"
| lines
| each {|file|
# Guard: Check if file can be opened and parsed as JSON (no try-catch)
let json_result = (do { open $file | from json } | complete)
if $json_result.exit_code == 0 {
let meta = ($json_result.stdout)
if ($parent_vm | is-empty) or ($meta.parent_vm == $parent_vm) {
{
name: $meta.name
image: $meta.image
parent_vm: $meta.parent_vm
cpu_millicores: $meta.cpu_millicores
memory_mb: $meta.memory_mb
status: $meta.status
created: $meta.created_at
}
}
} else {
null
}
}
| compact
}
export def "container-delete" [
name: string # Container name
]: record {
"""
Delete a container.
"""
let containers_dir = (get-containers-directory)
let meta_file = $"($containers_dir)/($name).json"
if (not ($meta_file | path exists)) {
return {success: false, error: "Container not found"}
}
# Delete container metadata (no try-catch)
let rm_result = (do { bash -c $"rm -f ($meta_file)" } | complete)
if $rm_result.exit_code != 0 {
return {success: false, error: $"Failed to delete container: ($rm_result.stderr)"}
}
{
success: true
message: "Container deleted"
}
}
export def "nesting-stats" []: record {
"""
Get nested provisioning statistics.
"""
let nested_vms = (nested-vm-list)
let containers = (container-list)
let max_depth = (
if ($nested_vms | length) > 0 {
$nested_vms | get depth | math max
} else {
0
}
)
{
nested_vms: ($nested_vms | length)
containers: ($containers | length)
max_nesting_depth: $max_depth
by_depth: (
$nested_vms
| group-by {|vm| $vm.depth}
| each {|group| {depth: ($group | first | get depth), count: ($group | length)}}
)
}
}
def get-nested-directory []: string {
"""Get nested VMs directory"""
"{{paths.workspace}}/vms/nested"
}
def get-containers-directory []: string {
"""Get containers directory"""
"{{paths.workspace}}/vms/containers"
}
def get-nesting-depth [vm: string]: int {
"""Get nesting depth of VM"""
let nested_dir = (get-nested-directory)
let meta_file = $"($nested_dir)/($vm).json"
if ($meta_file | path exists) {
let meta = (open $meta_file | from json)
$meta.nesting_depth
} else {
0
}
}