128 lines
5.5 KiB
Text
128 lines
5.5 KiB
Text
|
|
use ../../lib_provisioning/config/accessor.nu *
|
|||
|
|
use ../../lib_provisioning/utils/interface.nu [_print]
|
|||
|
|
use ../../workspace/state.nu *
|
|||
|
|
use ../../workspace/sync.nu *
|
|||
|
|
|
|||
|
|
export def handle_state_command [cmd: string, ops: string, flags: record] {
|
|||
|
|
let workspace_path = if ($env.PROVISIONING_WORKSPACE_PATH? | is-not-empty) {
|
|||
|
|
$env.PROVISIONING_WORKSPACE_PATH
|
|||
|
|
} else {
|
|||
|
|
$env.PWD
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let infra = ($flags | get -o infra | default "")
|
|||
|
|
let server = ($flags | get -o server | default "")
|
|||
|
|
let taskserv = ($flags | get -o taskserv | default "")
|
|||
|
|
let kubeconfig = ($flags | get -o kubeconfig | default "")
|
|||
|
|
|
|||
|
|
# When help_category == command name ("state"), the subcommand lands in $ops, not $cmd.
|
|||
|
|
let subcmd = if ($ops | is-not-empty) { ($ops | split row " " | first) } else { $cmd }
|
|||
|
|
|
|||
|
|
match $subcmd {
|
|||
|
|
"show" | "s" => {
|
|||
|
|
state-show $workspace_path --server $server
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
"init" | "i" => {
|
|||
|
|
let curr_settings = (find_get_settings --infra $infra)
|
|||
|
|
state-init $workspace_path $curr_settings
|
|||
|
|
_print $"State initialized at (state-path $workspace_path)"
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
"reset" | "r" => {
|
|||
|
|
if ($server | is-empty) or ($taskserv | is-empty) {
|
|||
|
|
error make { msg: "state reset requires --server <hostname> --taskserv <name>" }
|
|||
|
|
}
|
|||
|
|
state-node-reset $workspace_path $server $taskserv
|
|||
|
|
_print $"($server)/($taskserv) reset to pending"
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
"migrate" | "m" => {
|
|||
|
|
state-migrate-from-json $workspace_path
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
"sync" => {
|
|||
|
|
let curr_settings = (find_get_settings --infra $infra)
|
|||
|
|
|
|||
|
|
# 1. Drift detection + reconcile against servers.ncl
|
|||
|
|
let drift_rows = (state-drift $workspace_path $curr_settings --server $server)
|
|||
|
|
let has_drift = ($drift_rows | where drift != "ok" | is-not-empty)
|
|||
|
|
if $has_drift {
|
|||
|
|
_print "── drift ──"
|
|||
|
|
print ($drift_rows | where drift != "ok" | table)
|
|||
|
|
let result = (state-reconcile $workspace_path $curr_settings --server $server)
|
|||
|
|
if ($result.removed | is-not-empty) {
|
|||
|
|
_print $"🗑 Removed ($result.removed | length) orphaned"
|
|||
|
|
}
|
|||
|
|
if ($result.added | is-not-empty) {
|
|||
|
|
_print $"➕ Added ($result.added | length) pending"
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
_print "✅ No drift against servers.ncl"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 2. External API sync (Hetzner, K8s, SSH)
|
|||
|
|
let skip_ssh = ($flags | get -o skip_ssh | default false)
|
|||
|
|
state-sync $workspace_path $curr_settings --kubeconfig $kubeconfig --skip-ssh=$skip_ssh
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
"drift" | "d" => {
|
|||
|
|
let curr_settings = (find_get_settings --infra $infra)
|
|||
|
|
let rows = (state-drift $workspace_path $curr_settings --server $server)
|
|||
|
|
let has_drift = ($rows | where drift != "ok" | is-not-empty)
|
|||
|
|
if ($rows | is-empty) {
|
|||
|
|
_print "(no state entries to compare)"
|
|||
|
|
} else {
|
|||
|
|
print ($rows | table)
|
|||
|
|
if $has_drift {
|
|||
|
|
_print $"\n⚠ Drift detected. Run (_ansi yellow_bold)provisioning state reconcile(_ansi reset) to fix."
|
|||
|
|
} else {
|
|||
|
|
_print "\n✅ No drift — state matches servers.ncl"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
"reconcile" | "rec" => {
|
|||
|
|
let curr_settings = (find_get_settings --infra $infra)
|
|||
|
|
let dry_run = ($flags | get -o dry_run | default false)
|
|||
|
|
|
|||
|
|
# Always show drift first
|
|||
|
|
let drift_rows = (state-drift $workspace_path $curr_settings --server $server)
|
|||
|
|
let has_drift = ($drift_rows | where drift != "ok" | is-not-empty)
|
|||
|
|
if not $has_drift {
|
|||
|
|
_print "✅ No drift — nothing to reconcile"
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
print ($drift_rows | where drift != "ok" | table)
|
|||
|
|
|
|||
|
|
if $dry_run {
|
|||
|
|
_print "\n(dry-run: no changes applied)"
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let result = (state-reconcile $workspace_path $curr_settings --server $server)
|
|||
|
|
if ($result.removed | is-not-empty) {
|
|||
|
|
_print $"\n🗑 Removed ($result.removed | length) orphaned entries:"
|
|||
|
|
for r in $result.removed { _print $" ($r.server)/($r.taskserv)" }
|
|||
|
|
}
|
|||
|
|
if ($result.added | is-not-empty) {
|
|||
|
|
_print $"\n➕ Added ($result.added | length) pending entries:"
|
|||
|
|
for a in $result.added { _print $" ($a.server)/($a.taskserv)" }
|
|||
|
|
}
|
|||
|
|
_print "\n✅ State reconciled with servers.ncl"
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
_ => {
|
|||
|
|
_print "Usage: provisioning state <subcommand> [--infra <path>]"
|
|||
|
|
_print ""
|
|||
|
|
_print " show [--server <hostname>] — display state table"
|
|||
|
|
_print " init [--infra <path>] — bootstrap state from settings"
|
|||
|
|
_print " reset --server <hostname> --taskserv <name> — reset node to pending"
|
|||
|
|
_print " migrate — migrate .json → .ncl"
|
|||
|
|
_print " sync [--infra <path>] [--kubeconfig <path>] — reconcile from APIs"
|
|||
|
|
_print " drift [--infra <path>] [--server <hostname>] — detect state vs servers.ncl divergence"
|
|||
|
|
_print " reconcile [--infra <path>] [--server <hostname>] — fix drift (remove orphaned, add missing)"
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
}
|