provisioning-catalog/components/fip/taskserv/prepare

141 lines
5.8 KiB
Text

#!/usr/bin/env nu
# Prepare script for fip taskserv.
# Determines which FIPs apply to this server from floating_ips.ncl assignment policy,
# resolves their IP addresses, and writes FLOATING_IPS + IFACE to env-fip.
let vars_path = ($env.PROVISIONING_VARS? | default "")
if ($vars_path | is-empty) or not ($vars_path | path exists) {
print $"🛑 PROVISIONING_VARS not set or not found: ($vars_path)"
exit 1
}
let wk_data = (open $vars_path)
let iface = ($wk_data | get -o taskserv.interface | default "eth0")
let hostname = ($wk_data | get -o server.hostname | default "")
if ($hostname | is-empty) {
print "🛑 server.hostname not set in vars"
exit 1
}
# Resolve workspace root
let ws_root = do {
let explicit = ($env.PROVISIONING_WORKSPACE_PATH? | default "")
if ($explicit | is-not-empty) { $explicit } else {
let wss = ($env.PROVISIONING_WORKSPACES? | default "")
if ($wss | is-not-empty) { $wss | path dirname } else { "" }
}
}
# Load assignment policy from floating_ips.ncl
def load-fip-policy [ws_root: string]: nothing -> record {
let fips_ncl = ($ws_root | path join "infra/lib/floating_ips.ncl")
if not ($fips_ncl | path exists) { return {} }
let prov = ($env.PROVISIONING? | default ($ws_root | path join ".." ".." "provisioning" | path expand))
let import_args = ["--import-path" $prov, "--import-path" $ws_root]
let r = (do { ^nickel export --format json $fips_ncl ...$import_args } | complete)
if $r.exit_code != 0 { return {} }
$r.stdout | from json
}
# Determine which FIPs apply to this hostname based on assignment policy
def applicable-fips [fips: record, hostname: string, node_labels: record]: nothing -> list {
$fips | transpose key val | get val | where {|fip|
let mode = ($fip | get -o assignment.mode | default null)
match $mode {
"pinned" => {
($fip | get -o assignment.node | default "") == $hostname
},
"floating" => {
# Floating FIPs are owned end-to-end by fip-controller (follow-pod
# DaemonSet): it manages both the Hetzner assignment and the eth0 alias
# on the node currently running the workload. Statically pre-configuring
# them here on every can_use_fip-labelled node created a second writer,
# leaving stale aliases on non-home nodes (silent drift). The static
# taskserv now handles pinned FIPs only; cross-check with `just fip-reconcile`.
false
},
_ => { false },
}
}
}
# Load bootstrap state for IP resolution
let bs_data = do {
if ($ws_root | is-not-empty) {
let bs_path = ($ws_root | path join ".provisioning-state.json")
if ($bs_path | path exists) { open $bs_path } else { {} }
} else { {} }
}
def resolve-ip [fip_name: string, hostname: string, bs_data: record, ws_root: string]: nothing -> string {
# Bootstrap state is keyed by FIP name — always correct for multi-FIP scenarios.
# servers-state.floating_ip_address is only the server's primary assigned FIP, so
# checking it first gives the wrong IP when multiple FIPs apply to one server.
let fip_key = ($fip_name | str replace --all "librecloud-fip-" "" | str replace --all "-" "_")
let bs_rec = do -i { $bs_data.bootstrap.floating_ips | get $fip_key } | default null
if $bs_rec != null {
let addr = ($bs_rec | get -o ip | default "")
if ($addr | is-not-empty) {
print $"✓ ($fip_name) → ($addr) [bootstrap-state]"
return $addr
}
}
# Fallback: servers-state (single-FIP legacy, or bootstrap state missing)
if ($ws_root | is-not-empty) and ($hostname | is-not-empty) {
let infra_dir = ($ws_root | path join "infra")
if ($infra_dir | path exists) {
let candidates = (do -i { ls $infra_dir }
| where type == "dir"
| get name
| each {|d| $d | path join ".servers-state.json"}
| where {|p| $p | path exists})
for sc in $candidates {
let rec = (open $sc | get -o $hostname | default null)
if $rec != null {
let addr = ($rec | get -o floating_ip_address | default "")
if ($addr | is-not-empty) {
print $"✓ ($fip_name) → ($addr) [servers-state]"
return $addr
}
}
}
}
}
""
}
let fips = if ($ws_root | is-not-empty) { load-fip-policy $ws_root } else { {} }
let applicable = if ($fips | is-empty) {
# Fallback: legacy floating_ips array on server config
let arr = ($wk_data | get -o server.floating_ips | default [])
if ($arr | is-not-empty) {
$arr | each {|name| { name: $name }}
} else {
let singular = ($wk_data | get -o server.floating_ip | default "")
if ($singular | is-not-empty) { [{ name: $singular }] } else { [] }
}
} else {
let node_labels = ($wk_data | get -o server.node_labels | default {})
applicable-fips $fips $hostname $node_labels
}
if ($applicable | is-empty) {
print $"🛑 No FIPs applicable to ($hostname) — check floating_ips.ncl assignment policy"
exit 1
}
let resolved = ($applicable | each {|fip|
let addr = (resolve-ip $fip.name $hostname $bs_data $ws_root)
if ($addr | is-empty) {
print $"🛑 Cannot resolve IP for FIP '($fip.name)' on ($hostname) — run: provisioning bootstrap"
exit 1
}
$addr
})
let floating_ips_str = ($resolved | str join " ")
let out_dir = ($env.PROVISIONING_WK_ENV_PATH? | default ".")
$"IFACE=($iface)\nFLOATING_IPS=\"($floating_ips_str)\"\n" | save -f ($out_dir | path join "env-fip")
print $"✓ env-fip: IFACE=($iface) FLOATING_IPS=($floating_ips_str)"