provisioning-core/nulib/orchestration/servers/status.nu

332 lines
15 KiB
Text
Raw Normal View History

# Selective imports replacing `use lib_provisioning *` (ADR-025 Phase 4).
use primitives/infra/init.nu [get-provisioning-args get-provisioning-name provisioning_init]
use primitives/io/interface.nu [_print end_run set-provisioning-no-terminal set-provisioning-out]
use primitives/io/logging.nu [is-debug-enabled set-debug-enabled set-metadata-enabled]
use platform/settings/loader.nu [find_get_settings]
use primitives/io/undefined.nu [invalid_task]
use utils.nu *
use ssh.nu *
# Detailed view of a single server: settings + cache + DAG component states + optional hcloud describe.
export def "main show" [
name?: string
--infra (-i): string = ""
--settings (-s): string = ""
--out: string = "" # Output format: json | yaml | text (default)
--live
--debug (-x)
]: nothing -> nothing {
if $debug { set-debug-enabled true }
let curr_settings = (find_get_settings --infra $infra --settings $settings)
if (($curr_settings | describe) == "nothing") or ($curr_settings == null) {
error make { msg: "Unable to load server settings" }
}
let servers = if ($curr_settings | get data? | is-not-empty) {
$curr_settings.data | get servers? | default []
} else {
$curr_settings | get servers? | default []
}
if ($servers | is-empty) { print "No servers configured"; return }
# Interactive selector requires terminal — skip when --out is set
let hostname = if ($name | is-not-empty) {
$name
} else if ($out | is-not-empty) {
error make { msg: "Pass a server name when using --out" }
} else {
let choices = ($servers | each {|s| $s.hostname})
if ($choices | length) == 1 {
$choices | first
} else {
let sel = ($choices | input list "Select server:")
if ($sel | is-empty) { return }
$sel
}
}
let srv_def = ($servers | where {|s| $s.hostname == $hostname} | get 0?)
if $srv_def == null {
print $"❌ Server '($hostname)' not found in settings"
return
}
# Cache data
let state_cache = (read_servers_state_cache $curr_settings)
let cached = if $hostname in ($state_cache | columns) { $state_cache | get $hostname } else { null }
# DAG data from .provisioning-state.ncl
let infra_path_val = ($curr_settings | get -o infra_path | default "")
let infra_name_val = ($curr_settings | get -o infra | default "")
let infra_dir = if ($infra_name_val | is-not-empty) {
$infra_path_val | path join $infra_name_val
} else { $infra_path_val }
let dag_all = (read_infra_taskserv_states $infra_dir)
let server_dag = if $hostname in ($dag_all | columns) { $dag_all | get $hostname } else { [] }
# Structured record for --out support
let detail = {
hostname: $hostname
provider: ($srv_def | get -o provider | default "hetzner")
server_type: ($srv_def | get -o server_type | default "")
location: ($srv_def | get -o location | default "")
status: (if $cached != null { $cached | get -o status | default "—" } else { "—" })
public_ip: (if $cached != null { $cached | get -o public_ip | default "" } else { "" })
private_ip: ($srv_def | get -o networking.private_ip | default "")
floating_ip: (if $cached != null { $cached | get -o floating_ip_address | default "" } else { "" })
floating_ip_name: (if $cached != null { $cached | get -o floating_ip | default "" } else { "" })
protection: (if $cached != null { if ($cached | get -o protection_delete | default false) { "del" } else { "—" } } else { "—" })
cache_sync: (if $cached != null { $cached | get -o last_sync | default "" } else { "" })
dag: $server_dag
}
match $out {
"json" => {
let provider_data = (fetch-provider-record $hostname)
let full = if $provider_data != null { $detail | insert hcloud $provider_data } else { $detail }
print ($full | to json)
}
"yaml" => {
let provider_data = (fetch-provider-record $hostname)
let full = if $provider_data != null { $detail | insert hcloud $provider_data } else { $detail }
print ($full | to yaml)
}
_ => {
print $"== ($hostname) =="
print ""
print $"provider: ($detail.provider) ($detail.server_type) ($detail.location)"
if $cached != null {
print $"status: ($detail.status)"
print $"public ip: ($detail.public_ip)"
}
if ($detail.private_ip | is-not-empty) { print $"private ip: ($detail.private_ip)" }
if $cached != null {
if ($detail.floating_ip | is-not-empty) {
let fip_suffix = if ($detail.floating_ip_name | is-not-empty) { $" \(($detail.floating_ip_name)\)" } else { "" }
print $"floating: ($detail.floating_ip)($fip_suffix)"
}
print $"protection: ($detail.protection)"
let ts = ($detail.cache_sync | str replace "T" " " | str replace "Z" "")
if ($ts | is-not-empty) { print $"cache sync: ($ts)" }
}
if ($server_dag | is-not-empty) {
print ""
let total = ($server_dag | length)
let completed = ($server_dag | where { $in.state == "completed" } | length)
let failed = ($server_dag | where { $in.state == "failed" } | length)
let fail_note = if $failed > 0 { $" [($failed) failed]" } else { "" }
print $"DAG — ($completed)/($total) deployed($fail_note):"
for entry in ($server_dag | sort-by name) {
let icon = match $entry.state {
"completed" => "✅"
"failed" => "❌"
"running" => "🔄"
_ => "⏳"
}
let ended_at = ($entry | get -o ended_at | default "")
let started_at = ($entry | get -o started_at | default "")
let ts = if ($ended_at | is-not-empty) {
$ended_at | str replace "T" " " | str substring 0..15 | str replace "Z" ""
} else if ($started_at | is-not-empty) {
let t = ($started_at | str substring 0..15)
$"started ($t)"
} else { "" }
print $" ($icon) ($entry.name | fill -a l -w 20) ($entry.state | fill -a l -w 10) ($ts)"
}
}
print ""
hcloud-server-describe $hostname
}
}
}
# Fetch live provider data for a server as a structured record. Returns null if unavailable.
def fetch-provider-record [hostname: string]: nothing -> any {
let res = (do { ^hcloud server describe $hostname -o json } | complete)
if $res.exit_code != 0 { return null }
let s = ($res.stdout | from json)
let fip_res = (do { ^hcloud floating-ip list -o json } | complete)
let fips = if $fip_res.exit_code == 0 and ($fip_res.stdout | str trim | is-not-empty) {
$fip_res.stdout | from json | where {|f| ($f.server? | default null) == $s.id}
| each {|f| { name: $f.name, ip: $f.ip }}
} else { [] }
let vol_res = (do { ^hcloud volume list -o json } | complete)
let volumes = if $vol_res.exit_code == 0 and ($s.volumes? | default [] | is-not-empty) {
let vol_data = ($vol_res.stdout | from json)
$s.volumes | each {|vid|
$vol_data | where {|v| $v.id == $vid} | get 0?
| if $in != null { { name: $in.name, size_gb: $in.size } } else { { name: $"id:($vid)", size_gb: 0 } }
}
} else { [] }
{
id: ($s.id | into string)
name: $s.name
status: $s.status
server_type: ($s.server_type.name? | default "")
cores: ($s.server_type.cores? | default 0)
memory_gb: ($s.server_type.memory? | default 0)
disk_gb: ($s.server_type.disk? | default 0)
datacenter: ($s.datacenter.name? | default "")
location: ($s.datacenter?.location?.name? | default "")
public_ipv4: ($s.public_net?.ipv4?.ip? | default "")
public_ipv6: ($s.public_net?.ipv6?.ip? | default "")
private_ips: ($s.private_net? | default [] | each {|n|
let net = ($n | get -o network | default null)
let net_label = if $net == null { "" } else if ($net | describe) == "int" {
$"id:($net)"
} else {
$net | get -o name | default $"id:($net | get -o id | default '?')"
}
{ ip: $n.ip, network: $net_label }
})
floating_ips: $fips
volumes: $volumes
labels: ($s.labels? | default {})
created: ($s.created? | default "")
protection: { delete: ($s.protection?.delete? | default false), rebuild: ($s.protection?.rebuild? | default false) }
}
}
# Full server describe via hcloud CLI: id, status, IPs, type, datacenter, volumes, labels.
def hcloud-server-describe [hostname: string]: nothing -> nothing {
let res = (do { ^hcloud server describe $hostname -o json } | complete)
if $res.exit_code != 0 {
print $"hcloud server describe ($hostname) failed (exit ($res.exit_code)):"
print ($res.stderr | str trim)
return
}
let s = ($res.stdout | from json)
print $"server: ($s.name)"
print $"id: ($s.id)"
print $"status: ($s.status)"
print $"type: ($s.server_type.name) [($s.server_type.cores) vCPU ($s.server_type.memory)GB RAM ($s.server_type.disk)GB disk]"
print $"datacenter: ($s.datacenter.name) ($s.datacenter.location.description)"
let pub_ip = ($s.public_net.ipv4.ip? | default "—")
let pub_ip6 = ($s.public_net.ipv6.ip? | default "—")
print $"public IPv4: ($pub_ip)"
print $"public IPv6: ($pub_ip6)"
let priv_ips = ($s.private_net? | default [] | each {|n|
let net = ($n | get -o network | default null)
let net_label = if $net == null { "?" } else if ($net | describe) == "int" {
$"id:($net)"
} else {
$net | get -o name | default $"id:($net | get -o id | default '?')"
}
$"($n.ip) [($net_label)]"
} | str join ", ")
if ($priv_ips | is-not-empty) { print $"private IPs: ($priv_ips)" }
let fip_res = (do { ^hcloud floating-ip list -o json } | complete)
if $fip_res.exit_code == 0 and ($fip_res.stdout | str trim | is-not-empty) {
let fips = ($fip_res.stdout | from json | where {|f| ($f.server? | default null) == $s.id})
if ($fips | is-not-empty) {
let fip_str = ($fips | each {|f| $"($f.ip) [($f.name)]"} | str join ", ")
print $"floating IPs: ($fip_str)"
}
}
let vols = ($s.volumes? | default [])
if ($vols | is-not-empty) {
let vol_res = (do { ^hcloud volume list -o json } | complete)
if $vol_res.exit_code == 0 {
let vol_data = ($vol_res.stdout | from json)
let vol_str = ($vols | each {|vid|
let v = ($vol_data | where {|v| $v.id == $vid} | get 0?)
if ($v | is-not-empty) { $"($v.name) ($v.size)GB" } else { $"id:($vid)" }
} | str join ", ")
print $"volumes: ($vol_str)"
}
}
let labels = ($s.labels? | default {} | items {|k v| $"($k)=($v)"} | str join ", ")
if ($labels | is-not-empty) { print $"labels: ($labels)" }
print $"created: ($s.created)"
let prot = (if ($s.protection?.delete? | default false) { "delete" } else { "none" })
print $"protection: ($prot)"
}
# > Servers status
export def "main status" [
name?: string # Server hostname in settings
...args # Args for create command
--infra (-i): string # Infra directory
--settings (-s): string # Settings path
--outfile (-o): string # Output file
--serverpos (-p): int # Server position in settings
--check (-c) # Only check mode no servers will be created
--wait (-w) # Wait servers to be created
--live # Call hcloud server describe for full provider-level detail
--select: string # Select with task as option
--debug (-x) # Use Debug mode
--xm # Debug with PROVISIONING_METADATA
--xc # Debuc for task and services locally PROVISIONING_DEBUG_CHECK
--xr # Debug for remote servers PROVISIONING_DEBUG_REMOTE
--xld # Log level with DEBUG PROVISIONING_LOG_LEVEL=debug
--metadata # Error with metadata (-xm)
--notitles # not tittles
--helpinfo (-h) # For more details use options "help" (no dashes)
--out: string # Print Output format: json, yaml, text (default)
] {
if ($out | is-not-empty) {
set-provisioning-out $out
set-provisioning-no-terminal true
}
provisioning_init $helpinfo "servers status" $args
if $debug { set-debug-enabled true }
if $metadata { set-metadata-enabled true }
# --live + named server: full hcloud describe
if $live and ($name | is-not-empty) {
hcloud-server-describe $name
return
}
if $name != null and $name != "h" and $name != "help" {
let curr_settings = (find_get_settings --infra $infra --settings $settings)
if ($curr_settings.data.servers | find $name| length) == 0 {
_print $"🛑 invalid name ($name)"
exit 1
}
}
let task = if ($args | length) > 0 {
($args| get 0)
} else {
let str_task = (((get-provisioning-args) | str replace "create " " " ))
let str_task = if $name != null {
($str_task | str replace $name "")
} else {
$str_task
}
($str_task | str trim | split row " " | first | default "" | split row "-" | first | default "" | str trim)
}
let other = if ($args | length) > 0 { ($args| skip 1) } else { "" }
let ops = $"((get-provisioning-args)) " | str replace $" ($task) " "" | str trim
match $task {
"" if $name == "h" => {
^$"(get-provisioning-name)" -mod server create help --notitles
exit 0
},
"" if $name == "help" => {
^$"(get-provisioning-name)" -mod server create --help
_print (provisioning_options "create")
},
"" | "s" | "status" => {
let curr_settings = (find_get_settings --infra $infra --settings $settings)
if ($out | is-empty ) {
_print (mw_servers_info $curr_settings | table -i false)
} else {
_print (mw_servers_info $curr_settings | to json) "json" "result" "table"
}
},
_ => {
invalid_task "servers status" $task --end
}
}
# "" | "create"
if not $notitles and not (is-debug-enabled) { end_run "" }
}