use ../lib_provisioning/workspace * use ../lib_provisioning/utils/nickel_processor.nu [ncl-eval, ncl-eval-soft, default-ncl-paths] # Resolve the provisioning root for --import-path resolution. def comp-prov-root []: nothing -> string { $env.PROVISIONING? | default "/usr/local/provisioning" } # Export a Nickel file as parsed JSON. Uses default-ncl-paths to match the daemon's # cache key derivation — otherwise every call misses and re-runs nickel export cold. def comp-ncl-export [ws_root: string, rel_path: string]: nothing -> record { let full_path = ($ws_root | path join $rel_path) ncl-eval $full_path (default-ncl-paths $ws_root) } # Resolve workspace name: explicit --workspace flag or active workspace. def comp-resolve-workspace [workspace: string]: nothing -> string { if ($workspace | is-not-empty) { return $workspace } let details = (get-active-workspace-details) if ($details == null) { error make { msg: "No active workspace — pass --workspace or activate one first" } } $details.name } # Validate cluster capabilities against real infrastructure state. # # Exports infra/{infra}/capabilities.ncl from the workspace and compares declared # capabilities (storage_classes, ingress_class) against live kubectl output. # Returns a table of check / expected / actual / status rows. # # Usage: # provisioning validate capabilities --workspace libre-daoshi --infra wuji export def "main validate capabilities" [ --workspace (-w): string # Workspace name (default: active) --infra (-i): string = "wuji" # Infra name ]: nothing -> table { let ws_name = (comp-resolve-workspace $workspace) let ws_root = (get-workspace-path $ws_name) if ($ws_root | is-empty) { error make { msg: $"Workspace '($ws_name)' not found in registry." } } let caps_path = ($ws_root | path join "infra" $infra "capabilities.ncl") if not ($caps_path | path exists) { error make { msg: $"capabilities.ncl not found at ($caps_path)" } } let caps = (comp-ncl-export $ws_root ($"infra/($infra)/capabilities.ncl")) mut rows: list> = [] # Check storage classes let declared_sc = ($caps | get -o provides | default {} | get -o storage_classes | default [] | each { $in | into string }) if ($declared_sc | is-not-empty) { let sc_result = (do { ^kubectl get sc --no-headers -o custom-columns=NAME:.metadata.name } | complete) let actual_sc = if $sc_result.exit_code == 0 { $sc_result.stdout | lines | where { $in | is-not-empty } } else { [] } for sc in $declared_sc { let found = ($actual_sc | any { $in == $sc }) $rows = ($rows | append { check: "storage_class", expected: $sc, actual: (if $found { $sc } else { "" }), status: (if $found { "ok" } else { "MISSING" }), }) } } # Check ingress class let declared_ic = ($caps | get -o provides | default {} | get -o ingress_class | default "") if ($declared_ic | is-not-empty) { let ic_result = (do { ^kubectl get ingressclass --no-headers -o custom-columns=NAME:.metadata.name } | complete) let actual_ic = if $ic_result.exit_code == 0 { $ic_result.stdout | lines | where { $in | is-not-empty } } else { [] } let found = ($actual_ic | any { $in == $declared_ic }) $rows = ($rows | append { check: "ingress_class", expected: $declared_ic, actual: (if $found { $declared_ic } else { "" }), status: (if $found { "ok" } else { "MISSING" }), }) } $rows } # Validate component configuration against workspace capabilities and server inventory. # # Exports infra/{infra}/settings.ncl and checks each component: # - taskserv mode: verifies the target server exists in the servers map. # - cluster mode: verifies the storage_class (if declared) is in capabilities.storage_classes. # Returns a table of component / check / status / detail rows. # # Usage: # provisioning validate components --workspace libre-daoshi --infra wuji export def "main validate components" [ --workspace (-w): string # Workspace name (default: active) --infra (-i): string = "wuji" # Infra name ]: nothing -> table { let ws_name = (comp-resolve-workspace $workspace) let ws_root = (get-workspace-path $ws_name) if ($ws_root | is-empty) { error make { msg: $"Workspace '($ws_name)' not found in registry." } } let settings = (comp-ncl-export $ws_root ($"infra/($infra)/settings.ncl")) let components = ($settings | get -o components | default {}) # Load capabilities for storage_class cross-check (best-effort: skip if absent). let caps_path = ($ws_root | path join "infra" $infra "capabilities.ncl") let caps_sc: list = if ($caps_path | path exists) { let c = (comp-ncl-export $ws_root ($"infra/($infra)/capabilities.ncl")) $c | get -o provides | default {} | get -o storage_classes | default [] | each { $in | into string } } else { [] } # Load servers for taskserv target validation (best-effort). let servers_path = ($ws_root | path join "infra" $infra "servers.ncl") let server_names: list = if ($servers_path | path exists) { ncl-eval-soft $servers_path (default-ncl-paths $ws_root) {} | get -o servers | default {} | columns } else { [] } mut rows: list> = [] let comp_names = ($components | columns) for comp_name in $comp_names { let comp = ($components | get $comp_name) let mode = ($comp | get -o mode | default "cluster") if $mode == "taskserv" { let target = ($comp | get -o target | default "") if ($target | is-empty) { $rows = ($rows | append { component: $comp_name, check: "target_server", status: "WARN", detail: "mode=taskserv but no target specified" }) } else if ($server_names | is-empty) { $rows = ($rows | append { component: $comp_name, check: "target_server", status: "SKIP", detail: $"servers.ncl not available — cannot verify '($target)'" }) } else { let found = ($server_names | any { $in == $target }) $rows = ($rows | append { component: $comp_name, check: "target_server", status: (if $found { "ok" } else { "MISSING" }), detail: (if $found { $"target '($target)' exists" } else { $"target '($target)' not found in servers" }), }) } } else if $mode == "cluster" { let sc = ($comp | get -o storage_class | default "") if ($sc | is-not-empty) { if ($caps_sc | is-empty) { $rows = ($rows | append { component: $comp_name, check: "storage_class", status: "SKIP", detail: "capabilities.ncl not available" }) } else { let found = ($caps_sc | any { $in == $sc }) $rows = ($rows | append { component: $comp_name, check: "storage_class", status: (if $found { "ok" } else { "MISSING" }), detail: (if $found { $"storage_class '($sc)' available" } else { $"storage_class '($sc)' not in capabilities" }), }) } } } # Always emit a baseline row even when no sub-checks apply. if ($rows | where component == $comp_name | is-empty) { $rows = ($rows | append { component: $comp_name, check: "declared", status: "ok", detail: $"mode=($mode)" }) } } $rows } # List all components declared in the workspace infra settings. # # Reads infra/{infra}/settings.ncl and renders each component with its name, # mode, target or namespace, and version (if available in the component config). # Returns a table of name / mode / target / namespace / version rows. # # Usage: # provisioning component list --workspace libre-daoshi --infra wuji export def "main component list" [ --workspace (-w): string # Workspace name (default: active) --infra (-i): string = "wuji" # Infra name ]: nothing -> table { let ws_name = (comp-resolve-workspace $workspace) let ws_root = (get-workspace-path $ws_name) if ($ws_root | is-empty) { error make { msg: $"Workspace '($ws_name)' not found in registry." } } let settings = (comp-ncl-export $ws_root ($"infra/($infra)/settings.ncl")) let components = ($settings | get -o components | default {}) $components | columns | each { |comp_name| let comp = ($components | get $comp_name) { name: $comp_name, mode: ($comp | get -o mode | default "cluster"), target: ($comp | get -o target | default ""), namespace: ($comp | get -o namespace | default ""), version: ($comp | get -o version | default ""), } } } # Show the full unified view of a single component declaration. # # Exports infra/{infra}/components/{name}.ncl from the workspace. If that file # does not exist, falls back to the component entry in settings.ncl. # Returns a record with mode, target, namespace, requires, provides, and operations. # # Usage: # provisioning component info postgresql --workspace libre-daoshi --infra wuji export def "main component info" [ name: string # Component name --workspace (-w): string # Workspace name (default: active) --infra (-i): string = "wuji" # Infra name ]: nothing -> record { let ws_name = (comp-resolve-workspace $workspace) let ws_root = (get-workspace-path $ws_name) if ($ws_root | is-empty) { error make { msg: $"Workspace '($ws_name)' not found in registry." } } # Prefer the per-component NCL file; fall back to settings.ncl entry. let comp_ncl_path = ($ws_root | path join "infra" $infra "components" $"($name).ncl") let comp = if ($comp_ncl_path | path exists) { comp-ncl-export $ws_root ($"infra/($infra)/components/($name).ncl") } else { let settings = (comp-ncl-export $ws_root ($"infra/($infra)/settings.ncl")) let components = ($settings | get -o components | default {}) if not ($name in ($components | columns)) { error make { msg: $"Component '($name)' not declared in infra/($infra)/settings.ncl and no per-component NCL found at ($comp_ncl_path)" } } $components | get $name } { mode: ($comp | get -o mode | default "cluster"), target: ($comp | get -o target | default ""), namespace: ($comp | get -o namespace | default ""), version: ($comp | get -o version | default ""), requires: ($comp | get -o requires | default []), provides: ($comp | get -o provides | default {}), operations: ($comp | get -o operations | default []), } }