331 lines
15 KiB
Text
331 lines
15 KiB
Text
# 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 "" }
|
|
}
|