Complete the cluster decomposition: the 23 introspection helpers (capacity, kubectl/ssh summaries, live-check, badges) move from cli/components_cluster.nu to domain/cluster/introspect.nu. cli/components_cluster.nu now holds ONLY the 5 `main cluster *` verbs (963 -> 452 lines). introspect.nu imports downward only (tools/nickel, domain/cluster/access, platform/clients/ssh, primitives/logging). Exported 4 previously-private helpers (fmt-gib, k8s-quantity-bytes, node-operator-labels, cluster-top-legend) so the cli verbs can call them across the module boundary; dropped a dead comp-dag-summary import. component.nu repoints its 11 helper imports to introspect.nu. Verified: module-load of all touched modules (no cycle, no upward import); ADR-026 topology clean across the whole cluster subsystem; cluster --help + component list run. Concludes slices 2a+2b: cli/components.nu 3611 -> 2410 (-33%).
736 lines
37 KiB
Text
736 lines
37 KiB
Text
#!/usr/bin/env nu
|
|
# Thin entry for component commands.
|
|
# Bypasses full dispatcher — loads only components/mod.nu + targeted lib_provisioning.
|
|
# Mirrors the provisioning-taskserv.nu pattern for <1s startup.
|
|
|
|
export-env {
|
|
let lib_dirs_raw = ($env.NU_LIB_DIRS? | default "")
|
|
let current_lib_dirs = if ($lib_dirs_raw | type) == "string" {
|
|
if ($lib_dirs_raw | is-empty) { [] } else { ($lib_dirs_raw | split row ":") }
|
|
} else {
|
|
$lib_dirs_raw
|
|
}
|
|
let dynamic = ($env.PROVISIONING? | default "" | path join "core" "nulib")
|
|
$env.NU_LIB_DIRS = ([
|
|
"/opt/provisioning/core/nulib"
|
|
"/usr/local/provisioning/core/nulib"
|
|
] | append $current_lib_dirs | append (if ($dynamic | is-not-empty) { [$dynamic] } else { [] }))
|
|
}
|
|
|
|
use primitives/io/interface.nu [_ansi]
|
|
|
|
use cli/components.nu [
|
|
"main component list"
|
|
"main component info"
|
|
"main component mark-complete"
|
|
"main component bulk-mark-complete"
|
|
"main component drop-key"
|
|
"main component drop-server"
|
|
"main component state-check"
|
|
"main component state-normalize"
|
|
"main validate capabilities"
|
|
"main validate components"
|
|
"main component install"
|
|
"main component delete"
|
|
"main component update"
|
|
"main component reinstall"
|
|
"main component restart"
|
|
"main component backup"
|
|
"main component restore"
|
|
"main component check-updates"
|
|
"main component render"
|
|
comp-load-component-record
|
|
]
|
|
use tools/nickel/process.nu [comp-ncl-export]
|
|
use domain/cluster/access.nu [comp-kubectl-access]
|
|
use domain/workspace/resolve.nu [comp-state-server comp-ws-path comp-ws-infra]
|
|
use domain/cluster/introspect.nu [
|
|
comp-live-check
|
|
comp-pods-for
|
|
comp-ssh-pods
|
|
comp-ssh-k8s-data
|
|
comp-ssh-describe-pod
|
|
comp-load-server-info
|
|
comp-server-badge
|
|
comp-cluster-summary
|
|
comp-cluster-badge
|
|
comp-cache-last-sync
|
|
comp-settings-k0s-version
|
|
]
|
|
|
|
def state-color [s: string]: nothing -> string {
|
|
match $s {
|
|
"completed" => $"(_ansi green)($s)(_ansi reset)"
|
|
"running" => $"(_ansi yellow)($s)(_ansi reset)"
|
|
"failed" => $"(_ansi red)($s)(_ansi reset)"
|
|
"blocked" => $"(_ansi red)($s)(_ansi reset)"
|
|
"partial" => $"(_ansi yellow)($s)(_ansi reset)"
|
|
_ => $"(_ansi dark_gray)($s)(_ansi reset)"
|
|
}
|
|
}
|
|
|
|
def main [
|
|
...args: string
|
|
--workspace (-w): string = ""
|
|
--mode: string = ""
|
|
--out (-o): string = ""
|
|
--infra (-i): string = ""
|
|
--server (-s): string = ""
|
|
--op: string = "init" # render: which op's plan scripts to include
|
|
--bundle # render: also write <name>.tar.gz.b64
|
|
--check
|
|
--dry-run # alias for --check: validates bundle without submitting
|
|
--wait
|
|
--live
|
|
--ext
|
|
--json (-j)
|
|
--debug (-x)
|
|
--filter (-f): string = "" # substring on name, or col=val (state=failed, ns=monitoring)
|
|
]: nothing -> nothing {
|
|
if $debug { $env.PROVISIONING_DEBUG = "true" }
|
|
let effective_check = ($check or $dry_run)
|
|
|
|
# Args come in as: ["component", "ls"] or ["ls", "postgresql"] depending on dispatch
|
|
let rest = if (($args | length) > 0) and (($args | first) in ["component", "comp", "c"]) {
|
|
$args | skip 1
|
|
} else {
|
|
$args
|
|
}
|
|
let sub = ($rest | get 0? | default "")
|
|
let name = ($rest | get 1? | default "")
|
|
|
|
# Workspace resolution: explicit flag > active env > empty (ext_only view)
|
|
let ws = if ($workspace | is-not-empty) {
|
|
$workspace
|
|
} else {
|
|
$env.PROVISIONING_KLOUD? | default ""
|
|
}
|
|
|
|
let help_text = {
|
|
print "Component Management"
|
|
print "===================="
|
|
print ""
|
|
print "Usage: prvng component <subcommand> [options]"
|
|
print ""
|
|
print "Read operations:"
|
|
print " list [--workspace <ws>] [--live] Provisioning state per component (alias: ls, l)"
|
|
print " ll [--workspace <ws>] Same as list --live (alias for --live)"
|
|
print " lc [--workspace <ws>] Same as list --mode cluster (alias: list cluster)"
|
|
print " lcl [--workspace <ws>] Same as list --mode cluster --live (alias: lc + live)"
|
|
print " [--server <host>] Filter taskserv rows to one server"
|
|
print " [<host>] Positional server filter (same as --server)"
|
|
print " [--mode cluster|taskserv] Filter by mode"
|
|
print " [--filter <text>] Substring match on name (e.g. --filter prom)"
|
|
print " [--filter col=val] Per-column match: state=failed, ns=monitoring, srv=wrk-0"
|
|
print " [--json] Raw JSON output (flat table)"
|
|
print " show <name> [--workspace <ws>] [--live] Full definition + state history (alias: sh, info)"
|
|
print " status <name> [--workspace <ws>] [--live] Operational snapshot + state (alias: s, st)"
|
|
print ""
|
|
print "State column values:"
|
|
print " pending Task not yet started (or server never initialized)"
|
|
print " running Task in progress — or stuck if ended_at is empty"
|
|
print " completed Task finished successfully via orchestrator"
|
|
print " failed Task finished with error"
|
|
print " — Component not in any DAG formula and no state entry yet"
|
|
print ""
|
|
print "Write operations (require orchestrator):"
|
|
print " install <name> [--workspace <ws>] [--server <host>] [--check] [--wait]"
|
|
print " delete <name> [--workspace <ws>] [--server <host>] [--check] [--wait]"
|
|
print " update <name> [--workspace <ws>] [--server <host>] [--check] [--wait]"
|
|
print " reinstall <name> [--workspace <ws>] [--server <host>] [--check] [--wait]"
|
|
print " restart <name> [--workspace <ws>] [--server <host>] [--wait]"
|
|
print " backup <name> [--workspace <ws>] [--server <host>] [--wait]"
|
|
print " restore <name> [--workspace <ws>] [--server <host>] [--check] [--wait]"
|
|
print " check-updates <name> [--workspace <ws>] [--server <host>]"
|
|
print ""
|
|
print "State correction:"
|
|
print " mark-complete <name> [--server <host>] [--note <text>]"
|
|
print " Force state → completed with actor attribution. Use when:"
|
|
print " • Component deployed manually or by an agent without orchestrator callback"
|
|
print " • State stuck in 'running (ended_at empty) after successful deploy"
|
|
print " • Task completed via human intervention and needs verification record"
|
|
print " Alias: mc"
|
|
print ""
|
|
print " mark-complete-live [--workspace <ws>]"
|
|
print " Bulk mark-complete for all components where state=running AND live=healthy."
|
|
print " Fetches live k8s data (one SSH session) before deciding — only marks what"
|
|
print " is confirmed running by the live check strategy."
|
|
print " Alias: mcl"
|
|
print ""
|
|
print " mark-complete-all [--workspace <ws>]"
|
|
print " Bulk mark-complete for ALL components where state=running, no live check."
|
|
print " Use for components without live_check (os, vol_prepare, kubernetes, etc.)"
|
|
print " or when k8s is unreachable and state is known to be correct."
|
|
print " Alias: mca"
|
|
print ""
|
|
print " bulk-mark-complete [--include-failed] [--dry-run] [--note <text>]"
|
|
print " Single-write sweep: marks all state=running (ended_at empty) as completed."
|
|
print " --include-failed: also sweep state=failed entries (e.g. cilium/longhorn that"
|
|
print " failed on first attempt but are actually running — verify pods first)."
|
|
print " Alias: bmc"
|
|
print ""
|
|
print " state-check [--server <host>]"
|
|
print " Diagnose state incoherence: lists stale-running, failed-but-recovered,"
|
|
print " and duplicate kebab/snake keys. Use this first to understand what to fix."
|
|
print " Alias: sc"
|
|
print ""
|
|
print " drop-key <key> --server <host> [--dry-run]"
|
|
print " Remove a specific state key from a server's taskservs. Use to prune"
|
|
print " orphaned duplicate entries left by naming migrations (e.g. odoo-jesusperez"
|
|
print " coexisting with odoo_jesusperez)."
|
|
print " Alias: dk"
|
|
print ""
|
|
print " state-normalize [--dry-run]"
|
|
print " One-shot migration: merge all snake_case state keys into their kebab"
|
|
print " equivalents. Eliminates duplicate entries from previous naming inconsistency."
|
|
print " Run once, then verify with sc. Future writes always use kebab (fixed upstream)."
|
|
print " Alias: sn"
|
|
print ""
|
|
print "Flags:"
|
|
print " --live Fetch live pod status from k8s via SSH (k0s kubectl get pods)."
|
|
print " Shows actual pod counts — not a proxy for provisioning state."
|
|
print " --check / --dry-run Dry-run: validates bundle without submitting to orchestrator."
|
|
print " --wait Block until orchestrator task reaches terminal state."
|
|
print ""
|
|
print "Examples:"
|
|
print " prvng c l # provisioning state"
|
|
print " prvng c ll # + live pod status"
|
|
print " prvng c lc # cluster components only"
|
|
print " prvng c lcl # cluster components + live"
|
|
print " prvng c l libre-wuji-wrk-0 # taskserv for one server"
|
|
print " prvng c l --live --workspace libre-wuji"
|
|
print " prvng c mc postgresql --note 'verified: kubectl get sts -n data'"
|
|
print " prvng c install postgresql --wait"
|
|
print " prvng c sh postgresql --live"
|
|
}
|
|
|
|
match $sub {
|
|
"" => { do $help_text }
|
|
"list" | "ls" | "l" | "ll" | "lc" | "lcl" => {
|
|
let is_live = $live or ($sub == "ll") or ($sub == "lcl")
|
|
let effective_mode = if $sub in ["lc", "lcl"] {
|
|
"cluster"
|
|
} else if ($mode | is-not-empty) {
|
|
$mode
|
|
} else {
|
|
""
|
|
}
|
|
# Second positional arg is treated as a server filter for list ops.
|
|
let effective_server = if ($server | is-not-empty) { $server } else { $name }
|
|
let all_rows = with-env { PROVISIONING_NO_CACHE: "true" } {
|
|
if ($ws | is-not-empty) {
|
|
main component list --workspace $ws --mode $effective_mode --server $effective_server
|
|
} else {
|
|
main component list --mode $effective_mode --server $effective_server
|
|
}
|
|
}
|
|
|
|
let filtered_rows = if ($filter | is-not-empty) {
|
|
if ($filter | str contains "=") {
|
|
let parts = ($filter | split row "=" | take 2)
|
|
let col = ($parts | get 0)
|
|
let val = ($parts | get 1 | str downcase)
|
|
let col_map = { ns: "namespace", state: "state", deploy: "state", name: "name", srv: "server", server: "server" }
|
|
let real_col = ($col_map | get -o $col | default $col)
|
|
$all_rows | where { ($in | get -o $real_col | default "" | into string | str downcase) | str contains $val }
|
|
} else {
|
|
let term = ($filter | str downcase)
|
|
$all_rows | where { $in.name | str downcase | str contains $term }
|
|
}
|
|
} else {
|
|
$all_rows
|
|
}
|
|
let cluster_rows = ($filtered_rows | where mode == "cluster")
|
|
let taskserv_rows = ($filtered_rows | where mode == "taskserv")
|
|
|
|
# Single SSH session for all live checks (cluster + taskserv).
|
|
let ws_root_l = if ($ws | is-not-empty) { comp-ws-path $ws } else { comp-ws-path "" }
|
|
let infra_l = (comp-ws-infra $ws_root_l)
|
|
let state_root_l = if (($ws_root_l | path join "infra" $infra_l ".provisioning-state.ncl") | path exists) {
|
|
$ws_root_l | path join "infra" $infra_l
|
|
} else { $ws_root_l }
|
|
let ssh_srv = if $is_live { (comp-kubectl-access $ws_root_l $infra_l).host } else { "" }
|
|
let k8s_data = if $is_live {
|
|
let systemd_svcs = ($filtered_rows
|
|
| where { ($in | get -o live_check | default {} | get -o strategy | default "") == "systemd" }
|
|
| each { $in | get -o live_check | default {} | get -o service | default "" }
|
|
| where { $in | is-not-empty })
|
|
comp-ssh-k8s-data $ws_root_l $infra_l $ssh_srv --systemd-services $systemd_svcs
|
|
} else { {} }
|
|
|
|
# A taskserv's systemd service runs on the node it was deployed to — not the
|
|
# CP. The single CP gather above only sees cp-0's units, so a worker-only
|
|
# service (e.g. coredns-vpn-wrk1 on wrk-1) always shows inactive. Gather
|
|
# systemd per-server for taskserv rows whose server is not the CP, so each is
|
|
# checked on its own node. (live_check.scope is advisory; the deploy server is
|
|
# authoritative for where a taskserv's unit actually runs.)
|
|
let extra_systemd = if $is_live {
|
|
$taskserv_rows
|
|
| where { (($in | get -o live_check | default {} | get -o strategy | default "") == "systemd") and (($in.server? | default "") != $ssh_srv) and (($in.server? | default "") | is-not-empty) }
|
|
| group-by server
|
|
| items {|srv, rows|
|
|
let svcs = ($rows | each { $in | get -o live_check | default {} | get -o service | default "" } | where { $in | is-not-empty } | uniq)
|
|
{ server: $srv, systemd: ((comp-ssh-k8s-data $ws_root_l $infra_l $srv --systemd-services $svcs) | get -o systemd | default {}) }
|
|
}
|
|
| reduce --fold {} {|it, acc| $acc | insert $it.server $it.systemd }
|
|
} else { {} }
|
|
|
|
# If live data could not be fetched (SSH/kubectl unreachable), every row
|
|
# would misleadingly show "no pods". Warn once that it's a fetch failure,
|
|
# not pod absence.
|
|
if $is_live and ($k8s_data.pods | is-empty) and ($k8s_data.nodes | is-empty) {
|
|
print $"(_ansi yellow)⚠ live cluster data unavailable \(CP: ($ssh_srv | default 'unresolved'))\) — 'live' column reflects the failed fetch, not pod absence(_ansi reset)"
|
|
}
|
|
|
|
# Server info: cache by default, hcloud query when --live.
|
|
let srv_info = if ($taskserv_rows | is-not-empty) or ($cluster_rows | is-not-empty) {
|
|
if $is_live {
|
|
comp-load-server-info $ws_root_l $infra_l --live
|
|
} else {
|
|
comp-load-server-info $ws_root_l $infra_l
|
|
}
|
|
} else { {} }
|
|
|
|
if $json {
|
|
let components_out = ($filtered_rows | reject pod_selector live_check)
|
|
let servers_out = ($srv_info | items {|k v| {key: $k, value: $v}}
|
|
| reduce --fold {} {|it acc| $acc | insert $it.key $it.value})
|
|
{components: $components_out, servers: $servers_out} | to json | print
|
|
return
|
|
}
|
|
|
|
# ── Cluster section ──────────────────────────────────────────
|
|
if ($cluster_rows | is-not-empty) {
|
|
let _op_ws = (comp-ncl-export $ws_root_l $"infra/($infra_l)/settings.ncl"
|
|
| get -o settings | default {}
|
|
| get -o operator_workspace | default "")
|
|
let _cluster_header = if ($_op_ws | is-not-empty) {
|
|
$"(_ansi white_bold)CLUSTER COMPONENTS(_ansi reset) (_ansi dark_gray)→ hosted on(_ansi reset) (_ansi cyan)($_op_ws)(_ansi reset)"
|
|
} else {
|
|
$"(_ansi white_bold)CLUSTER COMPONENTS(_ansi reset)"
|
|
}
|
|
print $_cluster_header
|
|
print $"(_ansi dark_gray)────────────────────────────────────────────────────(_ansi reset)"
|
|
let cl_version = (comp-settings-k0s-version $ws_root_l $infra_l)
|
|
let cl_summary = if $is_live {
|
|
comp-cluster-summary $ws_root_l $infra_l $k8s_data $cl_version --live
|
|
} else {
|
|
comp-cluster-summary $ws_root_l $infra_l {} $cl_version
|
|
}
|
|
let cl_badge = (comp-cluster-badge $cl_summary)
|
|
if ($cl_badge | is-not-empty) {
|
|
print $" (_ansi dark_gray)($cl_badge)(_ansi reset)"
|
|
}
|
|
if $is_live {
|
|
$cluster_rows | each {|row|
|
|
let check = (comp-live-check $row.name $row $k8s_data)
|
|
let live_val = if $check.live == "—" {
|
|
$"(_ansi dark_gray)($check.detail)(_ansi reset)"
|
|
} else { $check.detail }
|
|
$row | select name namespace version class resources placement state | update state {|r| state-color $r.state} | rename --column {state: deploy} | insert live $live_val
|
|
} | table | print
|
|
} else {
|
|
$cluster_rows | select name namespace version class resources placement state | update state {|r| state-color $r.state} | rename --column {state: deploy} | table | print
|
|
}
|
|
print ""
|
|
}
|
|
|
|
# ── Taskserv section ─────────────────────────────────────────
|
|
if ($taskserv_rows | is-not-empty) {
|
|
print $"(_ansi white_bold)TASKSERV COMPONENTS(_ansi reset)"
|
|
let servers = ($taskserv_rows | get server | uniq | sort)
|
|
for srv in $servers {
|
|
print ""
|
|
let info = ($srv_info | get -o $srv | default {})
|
|
let badge = (comp-server-badge $info)
|
|
if ($badge | is-not-empty) {
|
|
print $" (_ansi white_bold)($srv)(_ansi reset) (_ansi dark_gray)($badge)(_ansi reset)"
|
|
} else {
|
|
print $" (_ansi white_bold)($srv)(_ansi reset)"
|
|
}
|
|
print $" (_ansi dark_gray)──────────────────────────────────────────────(_ansi reset)"
|
|
let srv_rows = ($taskserv_rows | where server == $srv | sort-by name)
|
|
if $is_live {
|
|
# Use this server's own systemd state (gathered per-server above)
|
|
# so a taskserv unit is checked on the node it runs on, not the CP.
|
|
let srv_k8s = ($k8s_data | upsert systemd (if ($srv == $ssh_srv) { ($k8s_data.systemd? | default {}) } else { ($extra_systemd | get -o $srv | default {}) }))
|
|
$srv_rows | each {|row|
|
|
let check = (comp-live-check $row.name $row $srv_k8s)
|
|
let live_val = if $check.live == "—" {
|
|
$"(_ansi dark_gray)($check.detail)(_ansi reset)"
|
|
} else { $check.detail }
|
|
{ name: $row.name, deploy: (state-color $row.state), live: $live_val }
|
|
} | table | str trim | lines | each {|ln| print $" ($ln)"}
|
|
} else {
|
|
$srv_rows
|
|
| select name state
|
|
| update state {|r| state-color $r.state}
|
|
| rename --column {state: deploy}
|
|
| table | str trim | lines | each {|ln| print $" ($ln)"}
|
|
}
|
|
}
|
|
print ""
|
|
}
|
|
if not $is_live {
|
|
let ts = (comp-cache-last-sync $ws_root_l $infra_l)
|
|
if ($ts | is-not-empty) {
|
|
print $"(_ansi dark_gray)from cache updated ($ts)(_ansi reset)"
|
|
}
|
|
}
|
|
}
|
|
"status" | "st" | "s" => {
|
|
if ($name | is-empty) {
|
|
print "Error: component status requires a name"
|
|
print "Usage: prvng component status <name> [--workspace <ws>]"
|
|
return
|
|
}
|
|
let ws_root_s = if ($ws | is-not-empty) { comp-ws-path $ws } else { comp-ws-path "" }
|
|
let infra_s = (comp-ws-infra $ws_root_s)
|
|
let info = (comp-load-component-record $name $ws_root_s $infra_s)
|
|
let dag = ($info.dag? | default [])
|
|
let overall = if ($dag | is-empty) {
|
|
"— (no DAG)"
|
|
} else if ($dag | all { $in.state == "completed" }) {
|
|
"✅ deployed"
|
|
} else if ($dag | any { $in.state == "running" }) {
|
|
"🔄 running"
|
|
} else if ($dag | any { $in.state == "failed" }) {
|
|
"❌ failed"
|
|
} else if ($dag | any { $in.state == "blocked" }) {
|
|
"⊘ blocked"
|
|
} else if ($dag | any { $in.state == "unknown" }) {
|
|
"? unknown (reset/reprovisioned)"
|
|
} else {
|
|
"⏳ pending"
|
|
}
|
|
print $"component: ($name)"
|
|
print $"state: ($overall)"
|
|
print $"mode: ($info.mode)"
|
|
print $"target: (if ($info.target | is-not-empty) { $info.target } else { '—' })"
|
|
print $"namespace: (if ($info.namespace | is-not-empty) { $info.namespace } else { '—' })"
|
|
print $"version: (if ($info.version | is-not-empty) { $info.version } else { '—' })"
|
|
let req_count = ($info.requires | length)
|
|
let ops_count = ($info.operations | length)
|
|
if $req_count > 0 { print $"requires: ($info.requires | str join ', ')" }
|
|
if $ops_count > 0 { print $"operations: ($ops_count) defined" }
|
|
if $live {
|
|
print ""
|
|
let dag_server = ($dag | get 0? | default null | get -o server | default "")
|
|
let k8s_data = (comp-ssh-k8s-data $ws_root_s $infra_s $dag_server)
|
|
let check = (comp-live-check $name $info $k8s_data)
|
|
let live_icon = match $check.live {
|
|
"healthy" => "✅",
|
|
"degraded" => "❌",
|
|
"starting" => "🔄",
|
|
"partial" => "⚠",
|
|
"not deployed" => "⊘",
|
|
_ => "—",
|
|
}
|
|
let live_label = if $check.live == "—" {
|
|
$"($live_icon) ($check.detail)"
|
|
} else {
|
|
$"($live_icon) ($check.live) ($check.detail)"
|
|
}
|
|
print $"live check: ($live_label)"
|
|
}
|
|
if ($dag | is-not-empty) {
|
|
print ""
|
|
print "DAG execution:"
|
|
for entry in $dag {
|
|
let state_icon = match $entry.state {
|
|
"completed" => "✅",
|
|
"running" => "🔄",
|
|
"failed" => "❌",
|
|
"blocked" => "⊘",
|
|
"unknown" => "?",
|
|
_ => "⏳",
|
|
}
|
|
let ts = if ($entry.ended_at | is-not-empty) {
|
|
$entry.ended_at | str replace "T" " " | str replace "Z" ""
|
|
} else if ($entry.started_at | is-not-empty) {
|
|
$"started ($entry.started_at | str replace 'T' ' ' | str replace 'Z' '')"
|
|
} else {
|
|
""
|
|
}
|
|
let blocker_suffix = if ($entry.blocker | is-not-empty) {
|
|
$" ← blocked by ($entry.blocker)"
|
|
} else { "" }
|
|
print $" ($state_icon) ($entry.formula | fill -a l -w 24) server: ($entry.server) ($entry.state | fill -a l -w 10) ($ts)($blocker_suffix)"
|
|
}
|
|
}
|
|
}
|
|
"show" | "info" | "sh" => {
|
|
if ($name | is-empty) {
|
|
print "Error: component show requires a name"
|
|
print "Usage: prvng component show <name> [--workspace <ws>]"
|
|
return
|
|
}
|
|
if ($ws | is-not-empty) {
|
|
main component info $name --workspace $ws
|
|
} else {
|
|
main component info $name
|
|
}
|
|
}
|
|
"install" => {
|
|
if ($name | is-empty) { print "Error: component install requires a name"; return }
|
|
let task_ids = if ($ws | is-not-empty) {
|
|
main component install $name --workspace $ws --server $server --check=$effective_check --wait=$wait
|
|
} else {
|
|
main component install $name --server $server --check=$effective_check --wait=$wait
|
|
}
|
|
$task_ids | each {|id| print $"task: ($id)"}
|
|
}
|
|
"delete" => {
|
|
if ($name | is-empty) { print "Error: component delete requires a name"; return }
|
|
let task_ids = if ($ws | is-not-empty) {
|
|
main component delete $name --workspace $ws --server $server --check=$effective_check --wait=$wait
|
|
} else {
|
|
main component delete $name --server $server --check=$effective_check --wait=$wait
|
|
}
|
|
$task_ids | each {|id| print $"task: ($id)"}
|
|
}
|
|
"update" => {
|
|
if ($name | is-empty) { print "Error: component update requires a name"; return }
|
|
let task_ids = if ($ws | is-not-empty) {
|
|
main component update $name --workspace $ws --server $server --check=$effective_check --wait=$wait
|
|
} else {
|
|
main component update $name --server $server --check=$effective_check --wait=$wait
|
|
}
|
|
$task_ids | each {|id| print $"task: ($id)"}
|
|
}
|
|
"reinstall" => {
|
|
if ($name | is-empty) { print "Error: component reinstall requires a name"; return }
|
|
let task_ids = if ($ws | is-not-empty) {
|
|
main component reinstall $name --workspace $ws --server $server --check=$effective_check --wait=$wait
|
|
} else {
|
|
main component reinstall $name --server $server --check=$effective_check --wait=$wait
|
|
}
|
|
$task_ids | each {|id| print $"task: ($id)"}
|
|
}
|
|
"restart" => {
|
|
if ($name | is-empty) { print "Error: component restart requires a name"; return }
|
|
let task_ids = if ($ws | is-not-empty) {
|
|
main component restart $name --workspace $ws --server $server --wait=$wait
|
|
} else {
|
|
main component restart $name --server $server --wait=$wait
|
|
}
|
|
$task_ids | each {|id| print $"task: ($id)"}
|
|
}
|
|
"backup" => {
|
|
if ($name | is-empty) { print "Error: component backup requires a name"; return }
|
|
let task_ids = if ($ws | is-not-empty) {
|
|
main component backup $name --workspace $ws --server $server --wait=$wait
|
|
} else {
|
|
main component backup $name --server $server --wait=$wait
|
|
}
|
|
$task_ids | each {|id| print $"task: ($id)"}
|
|
}
|
|
"restore" => {
|
|
if ($name | is-empty) { print "Error: component restore requires a name"; return }
|
|
let task_ids = if ($ws | is-not-empty) {
|
|
main component restore $name --workspace $ws --server $server --check=$effective_check --wait=$wait
|
|
} else {
|
|
main component restore $name --server $server --check=$effective_check --wait=$wait
|
|
}
|
|
$task_ids | each {|id| print $"task: ($id)"}
|
|
}
|
|
"check-updates" => {
|
|
if ($name | is-empty) { print "Error: component check-updates requires a name"; return }
|
|
let task_ids = if ($ws | is-not-empty) {
|
|
main component check-updates $name --workspace $ws --server $server
|
|
} else {
|
|
main component check-updates $name --server $server
|
|
}
|
|
$task_ids | each {|id| print $"task: ($id)"}
|
|
}
|
|
"mark-complete" | "mc" => {
|
|
if ($name | is-empty) { print "Error: component mark-complete requires a name"; return }
|
|
if ($ws | is-not-empty) {
|
|
main component mark-complete $name --workspace $ws --server $server
|
|
} else {
|
|
main component mark-complete $name --server $server
|
|
}
|
|
}
|
|
"mark-complete-live" | "mcl" => {
|
|
let ws_root = if ($ws | is-not-empty) { comp-ws-path $ws } else { comp-ws-path "" }
|
|
let infra = (comp-ws-infra $ws_root)
|
|
let state_root = if (($ws_root | path join "infra" $infra ".provisioning-state.ncl") | path exists) {
|
|
$ws_root | path join "infra" $infra
|
|
} else { $ws_root }
|
|
let rows = with-env { PROVISIONING_NO_CACHE: "true" } {
|
|
if ($ws | is-not-empty) {
|
|
main component list --workspace $ws --mode $mode
|
|
} else {
|
|
main component list --mode $mode
|
|
}
|
|
}
|
|
let ssh_server = (comp-state-server $state_root)
|
|
let k8s_data = (comp-ssh-k8s-data $ws_root $infra $ssh_server)
|
|
let candidates = (
|
|
$rows
|
|
| where { $in.state in ["running", "failed"] }
|
|
| each {|row|
|
|
let check = (comp-live-check $row.name $row $k8s_data)
|
|
if $check.live == "healthy" {
|
|
[{ name: $row.name, server: ($row.server | default ""), prior_state: $row.state }]
|
|
} else { [] }
|
|
}
|
|
| flatten
|
|
)
|
|
if ($candidates | is-empty) {
|
|
print "No components with state=running/failed and live=healthy found."
|
|
return
|
|
}
|
|
print $"Marking ($candidates | length) components complete:"
|
|
for c in $candidates {
|
|
print $" → ($c.name) [($c.prior_state) → completed]"
|
|
let srv = if ($c.server | is-not-empty) { $c.server } else { $server }
|
|
try {
|
|
if ($ws | is-not-empty) {
|
|
main component mark-complete $c.name --workspace $ws --server $srv
|
|
} else {
|
|
main component mark-complete $c.name --server $srv
|
|
}
|
|
} catch {|e|
|
|
print $" (_ansi yellow)skipped: ($e.msg)(_ansi reset)"
|
|
}
|
|
}
|
|
}
|
|
"mark-complete-all" | "mca" => {
|
|
let ws_root = if ($ws | is-not-empty) { comp-ws-path $ws } else { comp-ws-path "" }
|
|
let infra = (comp-ws-infra $ws_root)
|
|
let state_root = if (($ws_root | path join "infra" $infra ".provisioning-state.ncl") | path exists) {
|
|
$ws_root | path join "infra" $infra
|
|
} else { $ws_root }
|
|
let rows = with-env { PROVISIONING_NO_CACHE: "true" } {
|
|
if ($ws | is-not-empty) {
|
|
main component list --workspace $ws --mode $mode
|
|
} else {
|
|
main component list --mode $mode
|
|
}
|
|
}
|
|
let ssh_server = (comp-state-server $state_root)
|
|
let candidates = (
|
|
$rows
|
|
| where { $in.state == "running" }
|
|
| each {|row|
|
|
[{ name: $row.name, server: ($row.server | default "") }]
|
|
}
|
|
| flatten
|
|
)
|
|
if ($candidates | is-empty) {
|
|
print "No components with state=running found."
|
|
return
|
|
}
|
|
print $"Marking ($candidates | length) components complete \(no live check\):"
|
|
for c in $candidates {
|
|
print $" → ($c.name)"
|
|
let srv = if ($c.server | is-not-empty) { $c.server } else { $ssh_server }
|
|
try {
|
|
if ($ws | is-not-empty) {
|
|
main component mark-complete $c.name --workspace $ws --server $srv
|
|
} else {
|
|
main component mark-complete $c.name --server $srv
|
|
}
|
|
} catch {|e|
|
|
print $" (_ansi yellow)skipped: ($e.msg)(_ansi reset)"
|
|
}
|
|
}
|
|
}
|
|
"bulk-mark-complete" | "bmc" => {
|
|
let include_failed = ($rest | any { $in == "--include-failed" })
|
|
let is_dry = ($dry_run or $check or ($rest | any { $in == "--dry-run" }))
|
|
if $is_dry {
|
|
if ($ws | is-not-empty) {
|
|
main component bulk-mark-complete --workspace $ws --infra $infra --dry-run
|
|
} else {
|
|
main component bulk-mark-complete --infra $infra --dry-run
|
|
}
|
|
} else if $include_failed {
|
|
if ($ws | is-not-empty) {
|
|
main component bulk-mark-complete --workspace $ws --infra $infra --include-failed
|
|
} else {
|
|
main component bulk-mark-complete --infra $infra --include-failed
|
|
}
|
|
} else {
|
|
if ($ws | is-not-empty) {
|
|
main component bulk-mark-complete --workspace $ws --infra $infra
|
|
} else {
|
|
main component bulk-mark-complete --infra $infra
|
|
}
|
|
}
|
|
}
|
|
"state-check" | "sc" => {
|
|
if ($ws | is-not-empty) {
|
|
main component state-check --workspace $ws --infra $infra --server $server
|
|
} else {
|
|
main component state-check --infra $infra --server $server
|
|
}
|
|
}
|
|
"state-normalize" | "sn" => {
|
|
let is_dry = ($dry_run or $check)
|
|
if $is_dry {
|
|
if ($ws | is-not-empty) {
|
|
main component state-normalize --workspace $ws --infra $infra --dry-run
|
|
} else {
|
|
main component state-normalize --infra $infra --dry-run
|
|
}
|
|
} else {
|
|
if ($ws | is-not-empty) {
|
|
main component state-normalize --workspace $ws --infra $infra
|
|
} else {
|
|
main component state-normalize --infra $infra
|
|
}
|
|
}
|
|
}
|
|
"drop-server" | "ds" => {
|
|
if ($name | is-empty) { print "Error: drop-server requires a hostname"; return }
|
|
let is_dry = ($dry_run or $check)
|
|
if $is_dry {
|
|
if ($ws | is-not-empty) {
|
|
main component drop-server $name --workspace $ws --infra $infra --dry-run
|
|
} else {
|
|
main component drop-server $name --infra $infra --dry-run
|
|
}
|
|
} else {
|
|
if ($ws | is-not-empty) {
|
|
main component drop-server $name --workspace $ws --infra $infra
|
|
} else {
|
|
main component drop-server $name --infra $infra
|
|
}
|
|
}
|
|
}
|
|
"drop-key" | "dk" => {
|
|
if ($name | is-empty) { print "Error: drop-key requires a key name"; return }
|
|
if ($server | is-empty) { print "Error: drop-key requires --server <hostname>"; return }
|
|
let is_dry = ($dry_run or $check)
|
|
if $is_dry {
|
|
if ($ws | is-not-empty) {
|
|
main component drop-key $name --server $server --workspace $ws --infra $infra --dry-run
|
|
} else {
|
|
main component drop-key $name --server $server --infra $infra --dry-run
|
|
}
|
|
} else {
|
|
if ($ws | is-not-empty) {
|
|
main component drop-key $name --server $server --workspace $ws --infra $infra
|
|
} else {
|
|
main component drop-key $name --server $server --infra $infra
|
|
}
|
|
}
|
|
}
|
|
"render" => {
|
|
if ($name | is-empty) { print "Error: component render requires a name"; return }
|
|
if $bundle {
|
|
main component render $name --op $op --workspace $ws --infra $infra --out $out --bundle
|
|
} else {
|
|
main component render $name --op $op --workspace $ws --infra $infra --out $out
|
|
}
|
|
}
|
|
"help" | "h" | "-h" | "--help" => { do $help_text }
|
|
_ => {
|
|
print $"Unknown component subcommand: ($sub)"
|
|
print "Run: prvng component help"
|
|
exit 1
|
|
}
|
|
}
|
|
}
|