# Infrastructure Command Handlers # Handles: server, taskserv, cluster, infra commands use ../flags.nu * # REMOVED: use ../../lib_provisioning * - causes circular import use ../../lib_provisioning/plugins/auth.nu * # Pre-load server module to preserve plugin context (tera, auth, kms, etc.) # This is needed so template rendering and other plugin operations work # in the same Nushell process use ../../servers/create.nu * # Helper to run module commands # Modules are pre-loaded above to preserve plugin context def run_module [ args: string module: string subcommand?: string # Optional explicit subcommand (for create operations) --exec ] { # Convert args string to list by splitting on spaces let args_list = if ($args | is-not-empty) { $args | split row " " | where {|x| ($x | str trim | is-not-empty) } } else { [] } # Call the appropriate module's main function # Server module is pre-loaded above, so plugins (tera, auth, kms, etc.) are in scope match $module { "server" => { # For server: call the "main create" function directly from the already-loaded servers/create.nu # This preserves the tera plugin context in the same process # If subcommand is explicitly provided (from handle_server), use it # Otherwise, extract from args let actual_subcommand = if ($subcommand | is-not-empty) { $subcommand } else { let op_list = ($args | split row " " | where { |x| ($x | is-not-empty) }) if ($op_list | length) > 0 { $op_list | first } else { "help" } } # For now, only handle "create" directly. For others, use -mod match $actual_subcommand { "create" | "c" | "list" | "l" => { # The servers/create.nu and servers/list.nu are loaded modules # Call "main create" or "main list" function directly with the arguments # This preserves context (env vars, plugins, etc.) in the same process let use_debug = if ($env.PROVISIONING_DEBUG? | default false) { "-x" } else { "" } let cmd_args = [-mod, "server", $actual_subcommand, ...$args_list] exec $"($env.PROVISIONING_NAME)" $use_debug ...$cmd_args } _ => { # For other operations (delete, ssh, price, status, etc.), use -mod with explicit subcommand let use_debug = if ($env.PROVISIONING_DEBUG? | default false) { "-x" } else { "" } let cmd_args = [-mod, "server", $actual_subcommand, ...$args_list] exec $"($env.PROVISIONING_NAME)" $use_debug ...$cmd_args } } } "taskserv" | "task" => { # Taskserv uses exec mode let use_debug = if ($env.PROVISIONING_DEBUG? | default false) { "-x" } else { "" } let cmd_args = [-mod, $module, ...$args_list, --notitles] exec $"($env.PROVISIONING_NAME)" $use_debug ...$cmd_args } "cluster" => { # Cluster uses exec mode let use_debug = if ($env.PROVISIONING_DEBUG? | default false) { "-x" } else { "" } let cmd_args = [-mod, $module, ...$args_list, --notitles] exec $"($env.PROVISIONING_NAME)" $use_debug ...$cmd_args } "infra" => { # Infra uses exec mode since it's a legacy module let use_debug = if ($env.PROVISIONING_DEBUG? | default false) { "-x" } else { "" } let cmd_args = [-mod, $module, ...$args_list, --notitles] exec $"($env.PROVISIONING_NAME)" $use_debug ...$cmd_args } _ => { print $"❌ Unknown module: ($module)" exit 1 } } } # Show infrastructure commands help def show_infrastructure_help [] { print "" print "INFRASTRUCTURE" print "" print " s server Server lifecycle — create, delete, list, ssh, price" print " t taskserv L2 provisioning — create, update, reset, delete, status" print " list → components filtered to mode=taskserv" print " show → component show" print " c component Unified component catalog and workspace instances" print " e component (ext) list [--mode taskserv|cluster|container] [--workspace ]" print " show [--workspace ] [--ext]" print " status --workspace " print " vm Virtual machine management" print "" print "ORCHESTRATION" print "" print " w workflow WorkflowDef lifecycle — list, show, run, validate, status" print " j job Orchestrator job management — list, status, monitor, submit" print " b batch Batch operations" print " o orchestrator Orchestrator daemon lifecycle" print "" print "Examples:" print " prvng c list # all components" print " prvng c list --mode cluster # cluster-mode only" print " prvng c show postgresql --workspace libre-daoshi # full component view" print " prvng c status k0s --workspace libre-daoshi # FSM state only" print " prvng w list --workspace libre-daoshi # workspace workflows" print " prvng w run deploy-services-libre-daoshi --workspace libre-daoshi" print " prvng t create --infra libre-daoshi # L2 provisioning" print " prvng s list # server list" print " prvng j list # orchestrator jobs" print "" } # Main infrastructure command dispatcher export def handle_infrastructure_command [ command: string ops: string flags: record ] { set_debug_env $flags match $command { "create" | "c" => { # Handle: provisioning create server/taskserv/cluster ... let create_ops_list = if ($ops | is-not-empty) { $ops | split row " " | where {|x| ($x | is-not-empty) } } else { [] } let resource_type = if (($create_ops_list | length) > 0) { $create_ops_list | first } else { "" } let resource_name_and_args = if (($create_ops_list | length) > 1) { $create_ops_list | skip 1 | str join " " } else { "" } match $resource_type { "server" | "s" => { let server_args = $"create ($resource_name_and_args)" handle_server $server_args $flags } "taskserv" | "task" | "t" => { let taskserv_args = $"create ($resource_name_and_args)" handle_taskserv $taskserv_args $flags } "cluster" | "cl" => { let cluster_args = $"create ($resource_name_and_args)" handle_cluster $cluster_args $flags } _ => { if ($resource_type | is-empty) { print "❌ Resource type required for create command" } else { print $"❌ Unknown resource type for create: ($resource_type)" } print "" print "Usage: provisioning create " print "" print "Resources:" print " server (s) - Create a server" print " taskserv (t) - Create a task service" print " cluster (cl) - Create a cluster" exit 1 } } } "delete" | "d" => { # Handle: provisioning delete server/taskserv/cluster ... let delete_ops_list = if ($ops | is-not-empty) { $ops | split row " " | where {|x| ($x | is-not-empty) } } else { [] } let resource_type = if (($delete_ops_list | length) > 0) { $delete_ops_list | first } else { "" } let resource_name_and_args = if (($delete_ops_list | length) > 1) { $delete_ops_list | skip 1 | str join " " } else { "" } match $resource_type { "server" | "s" => { let server_args = $"delete ($resource_name_and_args)" handle_server $server_args $flags } "taskserv" | "task" | "t" => { let taskserv_args = $"delete ($resource_name_and_args)" handle_taskserv $taskserv_args $flags } "cluster" | "cl" => { let cluster_args = $"delete ($resource_name_and_args)" handle_cluster $cluster_args $flags } _ => { print $"❌ Unknown resource type for delete: ($resource_type)" exit 1 } } } "bootstrap" | "bstrap" => { handle_bootstrap $ops $flags } "fip" | "floating-ip" => { handle_fip $ops $flags } "server" => { handle_server $ops $flags } "taskserv" | "task" => { handle_taskserv $ops $flags } "component" | "comp" => { handle_component $ops $flags } "extension" | "ext" => { handle_extension $ops $flags } "cluster" => { handle_component $ops $flags } # cluster → component (deprecated alias) "vm" => { # Import VM domain handler use vm_domain.nu handle_vm_command # Parse VM subcommand let vm_ops_list = if ($ops | is-not-empty) { $ops | split row " " | where {|x| ($x | is-not-empty) } } else { [] } let vm_command = if (($vm_ops_list | length) > 0) { $vm_ops_list | first } else { "vm" } let vm_remaining_ops = if (($vm_ops_list | length) > 1) { $vm_ops_list | skip 1 | str join " " } else { "" } handle_vm_command $vm_command $vm_remaining_ops $flags } "infra" | "infras" => { # Show help if no ops provided if ($ops | is-empty) { show_infrastructure_help } else { handle_infra $ops $flags } } "infrastructure" | "help" | "" => { show_infrastructure_help } _ => { print $"❌ Unknown command: ($command)" show_infrastructure_help exit 1 } } } # Floating IP command handler def handle_fip [ops: string, flags: record] { use ../../main_provisioning/fip.nu * let ops_list = if ($ops | is-not-empty) { $ops | split row " " | where {|x| ($x | is-not-empty) } } else { [] } let subcommand = if ($ops_list | length) > 0 { $ops_list | first } else { "" } let remaining = if ($ops_list | length) > 1 { $ops_list | skip 1 } else { [] } let out_flag = ($flags | get --optional output_format | default "") match $subcommand { "list" | "l" => { if ($out_flag | is-not-empty) { main list --out $out_flag } else { main list } } "show" | "s" => { let name = if ($remaining | length) > 0 { $remaining | first } else { error make { msg: "Usage: provisioning fip show " } } if ($out_flag | is-not-empty) { main show $name --out $out_flag } else { main show $name } } "assign" => { let name = if ($remaining | length) > 0 { $remaining | get 0 } else { error make { msg: "Usage: provisioning fip assign " } } let server = if ($remaining | length) > 1 { $remaining | get 1 } else { error make { msg: "Usage: provisioning fip assign " } } let yes = $flags.auto_confirm main assign $name $server --yes=$yes } "unassign" => { let name = if ($remaining | length) > 0 { $remaining | first } else { error make { msg: "Usage: provisioning fip unassign " } } let yes = $flags.auto_confirm main unassign $name --yes=$yes } "protection" => { let name = if ($remaining | length) > 0 { $remaining | get 0 } else { error make { msg: "Usage: provisioning fip protection " } } let action = if ($remaining | length) > 1 { $remaining | get 1 } else { error make { msg: "Usage: provisioning fip protection " } } main protection $name $action } _ => { print "Floating IP Management" print "=====================" print "" print "Usage: provisioning fip [args]" print "" print "Commands:" print " list List all Floating IPs with role and protection" print " show Show detail for a specific FIP" print " assign Assign FIP to a server" print " unassign Release FIP from its current server" print " protection enable|disable Toggle delete protection" print "" print "Examples:" print " provisioning fip list" print " provisioning fip show librecloud-fip-smtp" print " provisioning fip assign librecloud-fip-smtp sgoyol-1" print " provisioning fip unassign librecloud-fip-smtp" print " provisioning fip protection librecloud-fip-smtp enable" } } } # Bootstrap command handler — L1 Hetzner resource provisioning def handle_bootstrap [ops: string, flags: record] { use ../../main_provisioning/bootstrap.nu * let ws = ($flags | get --optional workspace | default "") let dry = $flags.dry_run if ($ws | is-not-empty) { main bootstrap --workspace $ws --dry-run=$dry } else { main bootstrap --dry-run=$dry } } # Server command handler def handle_server [ops: string, flags: record] { # Show help if no subcommand provided if ($ops | is-empty) { print "Server Management" print "=================" print "" print "Usage: provisioning server [options]" print "" print "Commands:" print " create Create a new server" print " delete Delete a server" print " list List all servers" print " ssh SSH into server" print " price Show server pricing" print "" print "Examples:" print " provisioning server create web-01" print " provisioning server list" print " provisioning server ssh web-01" print "" return } # Authentication check for server operations (metadata-driven) let operation_parts = ($ops | split row " " | where {|x| ($x | is-not-empty)}) let action = if ($operation_parts | is-empty) { "" } else { $operation_parts | first } # Determine operation type let operation_type = match $action { "create" | "c" => "create" "delete" | "d" | "remove" => "delete" "modify" | "update" => "modify" _ => "read" } # Check authentication using metadata-driven approach if not (is-check-mode $flags) and $operation_type != "read" { let operation_name = $"server ($action)" check-operation-auth $operation_name $operation_type $flags } # Extract the remaining arguments after the action verb (create/delete/list/etc) let action_and_args = if ($operation_parts | length) > 1 { $operation_parts | skip 1 | str join " " } else { "" } let args = build_module_args $flags $action_and_args # Pass the action as explicit subcommand so run_module knows which operation is being performed # For create operations, this preserves plugin context by calling "main create" directly run_module $args "server" $action --exec } # Task service command handler def handle_taskserv [ops: string, flags: record] { # Show help if no subcommand provided if ($ops | is-empty) { print "Task Service Management" print "======================" print "" print "Usage: provisioning taskserv [options]" print "" print "Commands:" print " create Create a task service" print " delete Delete a task service" print " list List all task services" print " generate Generate task service config" print "" print "Service Mesh Options:" print " istio - Full-featured service mesh with built-in ingress gateway" print " linkerd - Lightweight service mesh (requires external ingress)" print " cilium - CNI with service mesh capabilities" print "" print "Ingress Controller Options:" print " nginx-ingress - Most popular, battle-tested ingress controller" print " traefik - Modern cloud-native ingress with middleware" print " contour - Envoy-based ingress with simple configuration" print " haproxy-ingress - High-performance HAProxy-based ingress" print "" print "Examples:" print " provisioning taskserv create kubernetes" print " provisioning taskserv create istio" print " provisioning taskserv create linkerd" print " provisioning taskserv create nginx-ingress" print " provisioning taskserv create traefik" print " provisioning taskserv list" print "" print "Recommended Combinations:" print " 1. Linkerd + Nginx Ingress - Lightweight mesh + proven ingress" print " 2. Istio (standalone) - Full-featured with built-in gateway" print " 3. Linkerd + Traefik - Lightweight mesh + modern ingress" print " 4. No mesh + Nginx Ingress - Simple deployments" print "" return } # Authentication check for taskserv operations (metadata-driven) let operation_parts = ($ops | split row " ") let action = if ($operation_parts | is-empty) { "" } else { $operation_parts | first } # Determine operation type let operation_type = match $action { "create" | "c" => "create" "delete" | "d" | "remove" => "delete" "modify" | "update" => "modify" _ => "read" } # Check authentication using metadata-driven approach if not (is-check-mode $flags) and $operation_type != "read" { let operation_name = $"taskserv ($action)" check-operation-auth $operation_name $operation_type $flags } # Show ontoref FSM state from both ontology instances: # 1. provisioning project domain ($PROVISIONING/.ontology/) # 2. active workspace domain ($PROVISIONING_KLOUD_PATH/.ontology/) let ontoref_bin = (do { ^which ontoref } | complete | get stdout | str trim) if ($ontoref_bin | is-not-empty) { let prov_path = ($env.PROVISIONING? | default "") let kloud_path = ($env.PROVISIONING_KLOUD_PATH? | default "") let onto_roots = ( [$prov_path, $kloud_path] | where { |p| ($p | is-not-empty) and ($p | path join ".ontology" "state.ncl" | path exists) } | uniq ) if ($onto_roots | is-not-empty) { print "" for root in $onto_roots { do { cd $root; ^ontoref describe state } | complete | get stdout | print } } } let args = build_module_args $flags $ops run_module $args "taskserv" --exec } # Cluster command handler def handle_cluster [ops: string, flags: record] { # Show help if no subcommand provided if ($ops | is-empty) { print "Cluster Management" print "==================" print "" print "Usage: provisioning cluster [options]" print "" print "Commands:" print " deploy Deploy L3 platform or L4 app extensions" print " create Create a new cluster" print " delete Delete a cluster" print " list List all clusters" print "" print "Examples:" print " provisioning cluster deploy platform sgoyol --ws librecloud_renew" print " provisioning cluster deploy apps sgoyol --ws librecloud_renew" print " provisioning cluster create k8s-prod" print " provisioning cluster list" print "" return } let operation_parts = ($ops | split row " " | where { $in | is-not-empty }) let action = if ($operation_parts | is-empty) { "" } else { $operation_parts | first } # Intercept deploy — routes to cluster-deploy.nu, not the old -mod cluster module if $action in ["deploy"] { use ../../main_provisioning/cluster-deploy.nu * let rest = ($operation_parts | skip 1) let layer = ($rest | get -o 0 | default "") let cluster = ($rest | get -o 1 | default "") if ($layer | is-empty) or ($cluster | is-empty) { print "❌ Usage: provisioning cluster deploy [--ws ]" print " layer: platform | apps" exit 1 } let ws = ($flags | get --optional workspace | default "") let dry = $flags.dry_run let kube_cfg = "" let sec_file = "" if ($ws | is-not-empty) { main cluster deploy $layer $cluster --workspace $ws --dry-run=$dry --kubeconfig $kube_cfg --secrets-file $sec_file } else { main cluster deploy $layer $cluster --dry-run=$dry --kubeconfig $kube_cfg --secrets-file $sec_file } return } # Determine operation type for auth check let operation_type = match $action { "create" | "c" => "create" "delete" | "d" | "remove" | "destroy" => "delete" "modify" | "update" => "modify" _ => "read" } if not (is-check-mode $flags) and $operation_type != "read" { let operation_name = $"cluster ($action)" check-operation-auth $operation_name $operation_type $flags } let args = build_module_args $flags $ops run_module $args "cluster" --exec } # Infrastructure command handler def handle_infra [ops: string, flags: record] { # Handle infra-specific argument building let infra_arg = if ($flags.infra | is-not-empty) { $"-i ($flags.infra)" } else if ($flags.infras | is-not-empty) { $"--infras ($flags.infras)" } else { $"-i (get_infra | path basename)" } let use_yes = if $flags.auto_confirm { "--yes" } else { "" } let use_check = if $flags.check_mode { "--check" } else { "" } let use_onsel = if ($flags.onsel | is-not-empty) { $"--onsel ($flags.onsel)" } else { "" } let args = $"($ops) ($infra_arg) ($use_check) ($use_onsel) ($use_yes)" | str trim run_module $args "infra" } # Price/cost command handler export def handle_price_command [ops: string, flags: record] { let use_check = if $flags.check_mode { "--check " } else { "" } let str_infra = if ($flags.infra | is-not-empty) { $"--infra ($flags.infra) " } else { "" } let str_out = if ($flags.outfile | is-not-empty) { $"--outfile ($flags.outfile) " } else { "" } run_module $"($ops) ($str_infra) ($use_check) ($str_out)" "server" "price" --exec } # Create-server-task combined command handler export def handle_create_server_task [ops: string, flags: record] { # Create servers first let server_args = build_module_args $flags $ops run_module $server_args "server" "create" # Check if server creation succeeded if $env.LAST_EXIT_CODE != 0 { _print $"🛑 Errors found in (_ansi yellow_bold)create-server(_ansi reset)" exit 1 } # Create taskservs let taskserv_args = build_module_args $flags $"- ($ops)" run_module $taskserv_args "taskserv" "create" } # Component command handler — unified view for extensions/components def handle_component [ops: string, flags: record] { let parts = ($ops | split row " ") let action = if ($parts | is-empty) { "" } else { $parts | first } let workspace = ($flags.workspace? | default ($flags.ws? | default "")) let mode = ($flags.mode? | default "") use ../../components/mod.nu * match $action { "list" | "ls" | "l" | "" => { component-list $mode $workspace } "show" | "s" => { if ($parts | length) < 2 { print "❌ Error: component show requires a name" return } let name = ($parts | get 1) let ext_only = ($flags.ext? | default false) component-show $name $workspace $ext_only } "status" | "st" => { if ($parts | length) < 2 { print "❌ Error: component status requires a name" return } let name = ($parts | get 1) component-status $name $workspace } "" => { print "Component Management" print "====================" print "" print "Usage: provisioning component [options]" print "" print "Commands:" print " list [--mode taskserv|cluster|container] [--workspace ]" print " show [--workspace ] [--ext]" print " status [--workspace ]" print "" print "Examples:" print " provisioning component list" print " provisioning component list --mode cluster" print " provisioning component show postgresql --workspace libre-daoshi" print " provisioning component status k0s --workspace libre-daoshi" } _ => { print $"❌ Unknown component subcommand: ($action)" print "Use 'provisioning component' for help" } } } # Extension command handler — browses extension catalog (extensions/components/ definitions) # e / ext → extension → shows metadata, modes, requires/provides without workspace context def handle_extension [ops: string, flags: record] { let parts = ($ops | split row " ") let action = if ($parts | is-empty) { "" } else { $parts | first } let mode = ($flags.mode? | default "") use ../../components/mod.nu * match $action { "list" | "ls" | "l" | "" => { # Extension catalog: no workspace filter (ext_only view) component-list $mode "" } "show" | "s" => { if ($parts | length) < 2 { print "❌ Error: extension show requires a name" return } component-show ($parts | get 1) "" true # ext_only = true } _ => { print $"❌ Unknown extension subcommand: ($action)" print "Use: prvng e list | prvng e show " } } }