prvng_core/nulib/scripts/query-servers.nu
Jesús Pérez bea0477b25
refactor(18 files): selective imports — drive to 94.6% elimination (ADR-025 L2/L3)
Final mega-batch of single-star conversions combined in one commit.

=== Orchestrator facades (Layer 3, expanded to explicit symbol lists) ===
  config/accessor.nu            18 symbols (bridges accessor/mod)
  config/accessor_generated.nu  18 symbols (consumer of accessor)
  utils/version.nu              35 symbols (bridges version/mod)
  dependencies/mod.nu            7 symbols from resolver.nu
  oci_registry/mod.nu           12 multi-word "oci-registry X" subcommands
  oci/commands.nu               12 symbols from oci/client.nu
                                + removed redundant `use ./client.nu *` that
                                  was duplicated below the selective import

=== Selective imports (Layer 2) ===
  platform/discovery.nu         target.nu [5 symbols]
  platform/health.nu            target.nu [2 symbols]
  platform/connection.nu        user/config [get-active-workspace]
  vm/preparer.nu                vm/detector [check-vm-capability]
  vm/backend_libvirt.nu         result.nu [7 symbols]
  extensions/tests/test_versions.nu  versions [5 symbols]
  utils/version/loader.nu       nickel_processor [ncl-eval ncl-eval-soft]

=== Dead imports dropped ===
  platform/credentials.nu       user/config
  platform/activation.nu        target
  config/cache/core.nu          cache/metadata
  config/interpolation/core.nu  helpers/environment
  utils/version/loader.nu       version/core (kept nickel_processor)

=== Also included (pre-existing edits from earlier session) ===
  utils/settings.nu             pilot selective imports — reformatted
                                (file was modified externally during session)

Validation: all 18 files match pre-existing baselines (0 errors for clean
ones; 4/18/24/45/50/50 for pre-existing transitive noise).

MILESTONE: 94.6% of star-imports eliminated (370 → 20).

Remaining 20 star-lines in 5 files are intentional:
- lib_provisioning/mod.nu      (13 stars — root facade; empties in ADR-025 Phase 4)
- integrations/mod.nu          (2 stars — re-exports already-selective children)
- cmd/environment.nu           (3 stars — contains ~7 undefined function calls;
                                needs Blocker-1 style cleanup follow-up)
- providers/loader.nu          (1 dynamic `use ($entry_point) *` — runtime dispatch)
- vm/cleanup_scheduler.nu      (1 in string template — not a real import)

Refs: ADR-025
2026-04-17 17:10:47 +01:00

309 lines
13 KiB
Text
Executable file

# List all servers in active workspace
# This file is sourced by bash after lib_minimal.nu is loaded
# Not meant to be run standalone
# Usage: Called from bash with optional $INFRA_FILTER environment variable
# PWD-based workspace detection: if we're inside a workspace root that has
# config/provisioning.ncl, use it — takes precedence over the active workspace.
let pwd_config_file = ($env.PWD | path join "config" "provisioning.ncl")
use ../lib_provisioning/utils/nickel_processor.nu [ncl-eval-soft]
def extract-config-string [content: string, key: string] {
let pattern = ("(?m)^\\s*" + $key + "\\s*=\\s*\"(?<value>[^\"]+)\"")
let matches = ($content | parse --regex $pattern)
if ($matches | is-not-empty) {
$matches | first | get value | default ""
} else {
""
}
}
let pwd_config_raw = if ($pwd_config_file | path exists) {
open $pwd_config_file --raw
} else { "" }
let pwd_ws_config = if ($pwd_config_file | path exists) {
ncl-eval-soft $pwd_config_file [] {}
} else { {} }
let pwd_ws_name = (
$pwd_ws_config
| get --optional workspace
| default (extract-config-string $pwd_config_raw "workspace")
)
let pwd_current_infra = (
$pwd_ws_config
| get --optional current_infra
| default (extract-config-string $pwd_config_raw "current_infra")
)
# Convention fallback: if config/provisioning.ncl has no current_infra but
# infra/<pwd-basename>/settings.ncl exists, that's the default infra.
let pwd_convention_infra = if ($pwd_current_infra | is-empty) {
let candidate = ($env.PWD | path join "infra" ($env.PWD | path basename) | path join "settings.ncl")
if ($candidate | path exists) { $env.PWD | path basename } else { "" }
} else { "" }
let pwd_infra = if ($pwd_current_infra | is-not-empty) { $pwd_current_infra } else { $pwd_convention_infra }
# Resolve workspace: PWD-inferred takes precedence over session active workspace
let ws_path = if ($pwd_config_file | path exists) {
# We are inside the workspace root — PWD is the workspace path
$env.PWD
} else {
# Fall back to active workspace from user_config.yaml
let ws_result = (workspace-active)
let active_ws = if (is-ok $ws_result) { $ws_result.ok } else { "" }
if ($active_ws | is-empty) {
print 'No active workspace. Run: provisioning workspace activate <name>'
exit 1
}
let user_config_path = (
$env.HOME | path join 'Library' | path join 'Application Support'
| path join 'provisioning' | path join 'user_config.yaml'
)
if not ($user_config_path | path exists) {
print 'Config not found'
exit 1
}
let config = (open $user_config_path)
let workspaces = ($config | get --optional workspaces | default [])
let ws = ($workspaces | where { $in.name == $active_ws } | first)
if ($ws | is-empty) {
print $"Workspace '($active_ws)' not found in user config"
exit 1
}
$ws.path
}
let infra_path = ($ws_path | path join 'infra')
if not ($infra_path | path exists) {
print 'No infrastructures found'
exit 0
}
# Resolve filter: explicit INFRA_FILTER > PWD current_infra > convention > workspace default_infra
let filter_raw = ($env.INFRA_FILTER? | default "")
let filter = if ($filter_raw | is-not-empty) {
$filter_raw | path basename
} else if ($pwd_infra | is-not-empty) {
$pwd_infra
} else {
# Last resort: registered workspace default_infra from user config
let ws_result2 = (workspace-active)
let active_ws2 = if (is-ok $ws_result2) { $ws_result2.ok } else { "" }
if ($active_ws2 | is-not-empty) {
let uc = (
$env.HOME | path join 'Library' | path join 'Application Support'
| path join 'provisioning' | path join 'user_config.yaml'
)
if ($uc | path exists) {
let wlist = (open $uc | get --optional workspaces | default [])
let wentry = ($wlist | where { $in.name == $active_ws2 } | first)
$wentry | get --optional default_infra | default ""
} else { "" }
} else { "" }
}
# List server definitions from infrastructure (filtered if --infra specified)
let servers = (
ls $infra_path
| where type == 'dir'
| each {|infra|
let infra_name = ($infra.name | path basename)
# Skip if filter is specified and doesn't match
if (($filter | is-not-empty) and ($infra_name != $filter)) {
[]
} else {
# servers.ncl can live directly in infra dir or under defs/
let infra_dir = ($infra_path | path join $infra_name)
let servers_file_direct = ($infra_dir | path join 'servers.ncl')
let servers_file_defs = ($infra_dir | path join 'defs' | path join 'servers.ncl')
let servers_file = if ($servers_file_direct | path exists) {
$servers_file_direct
} else {
$servers_file_defs
}
if ($servers_file | path exists) {
# Parse servers.ncl: correlate hostname / server_type / private_ip per block.
# Strategy: scan lines in order; a new server block begins at each `make_server {`
# (or at the first hostname = "..." after the previous block closes).
# We accumulate fields until the next block starts.
let lines = (open $servers_file --raw | split row "\n")
let extract_quoted = {|line|
let parts = ($line | split row '"')
if ($parts | length) >= 2 { $parts | get 1 } else { "" }
}
# Build one record per server by scanning lines top-to-bottom.
# Reset on each `make_server {` boundary.
let parsed = (
$lines | reduce --fold {blocks: [], cur: {hostname: "", server_type: "", private_ip: ""}} {|line, acc|
let trimmed = ($line | str trim)
if ($trimmed =~ 'make_server\s*\{') {
# flush previous if it had a hostname
let blocks = if ($acc.cur.hostname | is-not-empty) {
$acc.blocks | append $acc.cur
} else {
$acc.blocks
}
{blocks: $blocks, cur: {hostname: "", server_type: "", private_ip: ""}}
} else if ($trimmed =~ '^hostname\s*=\s*"') {
{blocks: $acc.blocks, cur: ($acc.cur | upsert hostname (do $extract_quoted $trimmed))}
} else if ($trimmed =~ '^server_type\s*=\s*"') {
{blocks: $acc.blocks, cur: ($acc.cur | upsert server_type (do $extract_quoted $trimmed))}
} else if ($trimmed =~ '^private_ip\s*=\s*"') {
{blocks: $acc.blocks, cur: ($acc.cur | upsert private_ip (do $extract_quoted $trimmed))}
} else {
$acc
}
}
)
# flush last block
let all_blocks = if ($parsed.cur.hostname | is-not-empty) {
$parsed.blocks | append $parsed.cur
} else {
$parsed.blocks
}
$all_blocks
| where {|b| $b.hostname | is-not-empty }
| each {|b|
{
name: $b.hostname
infrastructure: $infra_name
server_type: $b.server_type
private_ip: $b.private_ip
path: $servers_file
}
}
} else {
[]
}
}
}
| flatten
)
# Read persisted server state (written by server_create workflow post-sync)
# Key: server name → { provider_id, public_ip, location, status, floating_ip, floating_ip_address }
let cached_state_path = ($ws_path | path join "infra" | path join $filter | path join ".servers-state.json")
let cached_state = if ($filter | is-not-empty) and ($cached_state_path | path exists) {
open $cached_state_path
} else { {} }
# Bootstrap state: FIP name → actual IP (fallback when server not in cached_state)
let bs_state_path = ($ws_path | path join ".provisioning-state.json")
let bs_fips = if ($bs_state_path | path exists) {
open $bs_state_path | get -o bootstrap.floating_ips | default {}
} else { {} }
# Query live status from hcloud for real-time status updates
let hcloud_res = (do { ^hcloud server list -o json } | complete)
let live_servers_all = if $hcloud_res.exit_code == 0 and ($hcloud_res.stdout | str trim | is-not-empty) {
let parsed = ($hcloud_res.stdout | from json)
if (($parsed | describe) | str starts-with "list") { $parsed } else { [] }
} else { [] }
let live_servers = if ($filter | is-not-empty) {
$live_servers_all | where {|l| ($servers | any {|s| $s.name == $l.name }) }
} else {
$live_servers_all
}
def status_icon [s: string] {
match $s {
"running" => "🟢"
"off" => "🔴"
"starting" => "🟡"
"stopping" => "🟡"
"rebuilding" => "🔵"
"migrating" => "🔵"
_ => "⚪"
}
}
if ($servers | length) == 0 {
print '📦 Available Servers: (none configured)'
} else {
print ''
let rows = ($servers | each {|srv|
let live = ($live_servers | where {|l| $l.name == $srv.name} | first | default null)
let cached = ($cached_state | get -o $srv.name | default null)
# Status: hcloud live > cached state > unknown
let status = if $live != null { $live.status } else if $cached != null { $cached.status } else { "—" }
# Public IP: hcloud live > cached state
let pub_ip = if $live != null {
$live.public_net?.ipv4?.ip? | default ""
} else if $cached != null {
$cached.public_ip? | default ""
} else { "" }
# Private IP: hcloud live (actual) > NCL (desired)
let priv_ip = if $live != null {
$live.private_net? | default [] | first | default null | get --optional ip | default ""
} else {
$srv.private_ip? | default ""
}
# Server type: hcloud live > NCL (type is config, not runtime state)
let srv_type = if $live != null {
$live.server_type?.name? | default ($srv.server_type? | default "")
} else {
$srv.server_type? | default ""
}
# Location: hcloud live > cached state
let location = if $live != null {
$live.datacenter?.location?.name? | default ""
} else if $cached != null {
$cached.location? | default ""
} else { "" }
# Floating IP: cached state (has name+ip) > bootstrap state lookup by FIP name
let fip_display = if $cached != null and ($cached.floating_ip? | default "" | is-not-empty) {
let fip_ip = ($cached.floating_ip_address? | default "")
if ($fip_ip | is-not-empty) {
$"($cached.floating_ip) ($fip_ip)"
} else {
$cached.floating_ip
}
} else {
# Fallback: resolve FIP IP from bootstrap state using the FIP name in NCL
let fip_name = ($srv | get -o floating_ip | default "")
if ($fip_name | is-not-empty) {
let fip_key = ($fip_name | str replace --all "librecloud-fip-" "" | str replace --all "-" "_")
let fip_rec = ($bs_fips | get -o $fip_key | default null)
if $fip_rec != null {
$"($fip_name) ($fip_rec.ip? | default "")"
} else { $fip_name }
} else { "" }
}
# Delete protection: hcloud live > cached state
let protected = if $live != null {
$live.protection?.delete? | default false
} else if $cached != null {
$cached.protection_delete? | default false
} else { false }
let lock_icon = if $protected { "🔒" } else { "" }
{
hostname: $srv.name
type: $srv_type
location: $location
status: $status
public_ip: $pub_ip
private_ip: $priv_ip
floating_ip: $fip_display
protected: $lock_icon
provider: "hetzner"
}
}
)
print ($rows | table -i false)
}