- DAG architecture: `dag show/validate/export` (nulib/main_provisioning/dag.nu),
config loader (lib_provisioning/config/loader/dag.nu), taskserv dag-executor.
Backed by schemas/lib/dag/*.ncl; orchestrator emits NATS events via
WorkspaceComposition::into_workflow. See ADR-020, ADR-021.
- Unified Component Architecture: components/mod.nu, main_provisioning/
{components,workflow,extensions,ontoref-queries}.nu. Full workflow engine with
topological sort and NATS subject emission. Blocks A-H complete (libre-daoshi).
- Commands-registry: nulib/commands-registry.ncl (Nickel source, 314 lines) +
JSON cache at ~/.cache/provisioning/commands-registry.json rebuilt on source
change. cli/provisioning fast-path alias expansion avoids cold Nu startup.
ADDING_COMMANDS.md documents new-command workflow.
- Platform service manager: service-manager.nu (+573), startup.nu (+611),
service-check.nu (+255); autostart/bootstrap/health/target refactored.
- Nushell 0.112.2 migration: removed all try/catch and bash redirections;
external commands prefixed with ^; type signatures enforced. Driven by
scripts/refactor-try-catch{,-simplified}.nu.
- TTY stack: removed shlib/*-tty.sh; replaced by cli/tty-dispatch.sh,
tty-filter.sh, tty-commands.conf.
- New domain modules: images/ (golden image lifecycle), workspace/{state,sync}.nu,
main_provisioning/{bootstrap,cluster-deploy,fip,state}.nu, commands/{state,
build,integrations/auth,utilities/alias}.nu, platform.nu expanded (+874).
- Config loader overhaul: loader/core.nu slimmed (-759), cache/core.nu
refactored (-454), removed legacy loaders/file_loader.nu (-330).
- Thirteen new provisioning-<domain>.nu top-level modules for bash dispatcher.
- Tests: test_workspace_state.nu (+351); updates to test_oci_registry,
test_services.
- README + CHANGELOG updated.
256 lines
11 KiB
Text
256 lines
11 KiB
Text
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<check: string, expected: string, actual: string, status: string> {
|
|
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<record<check: string, expected: string, actual: string, status: string>> = []
|
|
|
|
# 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 { "<not found>" }),
|
|
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 { "<not found>" }),
|
|
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<component: string, check: string, status: string, detail: string> {
|
|
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<string> = 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<string> = if ($servers_path | path exists) {
|
|
ncl-eval-soft $servers_path (default-ncl-paths $ws_root) {} | get -o servers | default {} | columns
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
mut rows: list<record<component: string, check: string, status: string, detail: string>> = []
|
|
|
|
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<name: string, mode: string, target: string, namespace: string, version: string> {
|
|
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 []),
|
|
}
|
|
}
|