provisioning-core/nulib/cli/handlers/runner_daemon.nu

173 lines
4.9 KiB
Text

# Runner daemon handler — list/destroy/pin/unpin/status via lian-build daemon HTTP API.
const DAEMON_DEFAULT_URL = "http://localhost:14150"
def daemon-url []: nothing -> string {
$env.LIAN_BUILD_DAEMON_URL? | default $DAEMON_DEFAULT_URL
}
def format-runner [r: record]: nothing -> record {
let ttl = if $r.lifecycle.ttl_minutes == 0 { "—" } else { $"($r.lifecycle.ttl_minutes)m" }
let sched = $r.lifecycle.destroy_at? | default "—"
{
id: ($r.id | str substring 0..<8)
workspace: $r.workspace
host: $"($r.ssh_host):($r.ssh_port)"
status: $r.status
pin: $r.lifecycle.pin
ttl: $ttl
destroy_at: $sched
spawned_at: $r.spawned_at
}
}
def runner-list [base_url: string]: nothing -> nothing {
let rows = try {
http get $"($base_url)/runners"
} catch {
print $"error: daemon unreachable at ($base_url)"
print " Start with: lian-build serve"
return
}
if ($rows | is-empty) {
print "no active runners"
return
}
$rows | each { |r| format-runner $r } | table
}
def runner-status [base_url: string, id: string]: nothing -> nothing {
if ($id | is-empty) {
print "error: runner status requires an id (full UUID or 8-char prefix)"
exit 1
}
let rows = try {
http get $"($base_url)/runners"
} catch {
print $"error: daemon unreachable at ($base_url)"
print " Start with: lian-build serve"
return
}
let matches = $rows | where { |r| ($r.id | str starts-with $id) }
if ($matches | is-empty) {
print $"error: no runner matching id '($id)'"
exit 1
}
$matches | first | table
}
def runner-destroy [base_url: string, id: string, confirmed: bool]: nothing -> nothing {
if not $confirmed {
print $"Destroy runner ($id)? This cannot be undone."
let answer = (input "Confirm [y/N]: " | str downcase | str trim)
if $answer != "y" {
print "aborted"
return
}
}
let result = try {
http delete $"($base_url)/runners/($id)"
{ ok: true }
} catch { |e|
{ ok: false, error: ($e | get msg? | default "request failed") }
}
if $result.ok {
print $"runner ($id) queued for destruction"
} else {
print $"error: ($result.error)"
exit 1
}
}
def runner-set-pin [base_url: string, id: string, pin: bool]: nothing -> nothing {
let endpoint = if $pin { "pin" } else { "unpin" }
let result = try {
http post $"($base_url)/runners/($id)/($endpoint)" {}
{ ok: true }
} catch { |e|
{ ok: false, error: ($e | get msg? | default "request failed") }
}
if $result.ok {
let verb = if $pin { "pinned" } else { "unpinned" }
print $"runner ($id) ($verb)"
} else {
print $"error: ($result.error)"
exit 1
}
}
def runner-help []: nothing -> nothing {
print "Runner Daemon Commands"
print "======================"
print ""
print "Usage: provisioning runner <command> [options]"
print ""
print "Commands:"
print " list List active runners"
print " status <id> Show full record for a runner (prefix match)"
print " destroy <id> Destroy a runner (prompts unless --yes)"
print " pin <id> Pin runner — daemon will not auto-destroy it"
print " unpin <id> Unpin runner — daemon resumes TTL/schedule enforcement"
print ""
print "Environment:"
print $" LIAN_BUILD_DAEMON_URL Daemon base URL (default: ($DAEMON_DEFAULT_URL))"
print ""
print "Examples:"
print " provisioning runner list"
print " provisioning runner status a1b2c3d4"
print " provisioning runner destroy a1b2c3d4 --yes"
print " provisioning runner pin a1b2c3d4"
}
export def handle_runner_command [
command: string
ops: string
flags: record
]: nothing -> nothing {
let url = daemon-url
let id = ($ops | str trim)
let confirmed = ($flags.auto_confirm? | default false)
match $command {
"list" | "l" => {
runner-list $url
}
"status" | "s" => {
runner-status $url $id
}
"destroy" | "d" => {
if ($id | is-empty) {
print "error: runner destroy requires an id"
exit 1
}
runner-destroy $url $id $confirmed
}
"pin" | "p" => {
if ($id | is-empty) {
print "error: runner pin requires an id"
exit 1
}
runner-set-pin $url $id true
}
"unpin" | "u" => {
if ($id | is-empty) {
print "error: runner unpin requires an id"
exit 1
}
runner-set-pin $url $id false
}
_ => {
runner-help
}
}
}