prvng_core/nulib/provisioning-volume.nu
Jesús Pérez 316622a78f
merge(refactor/lazy-loading): ADR-025 lazy-loading complete
557 files merged. Conflicts resolved:
  - CHANGELOG.md: took refactor/lazy-loading (session changelog)
  - versions.ncl: took refactor/lazy-loading (adds typedialog entries)
2026-04-17 23:09:56 +01:00

257 lines
8.4 KiB
Text
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env nu
# Volume management commands — hcloud-backed, workspace-aware.
use lib_provisioning/utils/interface.nu [_print set-provisioning-out set-provisioning-no-terminal]
use lib_provisioning/utils/settings.nu [find_get_settings]
use lib_provisioning/utils/nickel_processor.nu [ncl-eval-soft]
def main [
...args: string
--infra (-i): string = ""
--yes (-y)
--out: string = ""
]: nothing -> nothing {
if ($out | is-not-empty) {
set-provisioning-out $out
set-provisioning-no-terminal true
}
let subcmd = ($args | get 0? | default "")
let rest = if ($args | length) > 1 { $args | skip 1 } else { [] }
match $subcmd {
"list" | "l" => { main list --infra $infra --out $out }
"create" | "c" => {
let name = ($rest | get 0? | default "")
let size = ($rest | get 1? | default "20")
let loc = ($rest | get 2? | default "")
main create $name --size ($size | into int) --location $loc --yes=$yes
}
"attach" | "a" => {
let name = ($rest | get 0? | default "")
let server = ($rest | get 1? | default "")
main attach $name --server $server --yes=$yes
}
"detach" | "d" => {
let name = ($rest | get 0? | default "")
main detach $name --yes=$yes
}
"delete" | "rm" => {
let name = ($rest | get 0? | default "")
main delete $name --yes=$yes
}
"" | "help" => { show-volume-help }
_ => {
_print $"❌ Unknown volume subcommand: ($subcmd)"
show-volume-help
}
}
}
def show-volume-help [] {
_print "
Volume Management
=================
Usage: provisioning volume <command> [args]
Commands:
list List all volumes with attachment status
create <name> [size] [location] Create a new volume (default: 20GB, infra location)
attach <name> <server> Attach a volume to a server
detach <name> Detach a volume from its server
delete <name> Delete a volume (must be detached)
Examples:
prvng volume list
prvng volume create libre-daoshi-data 20 fsn1
prvng volume attach libre-daoshi-data libre-daoshi-0
prvng volume detach libre-daoshi-data
prvng volume delete libre-daoshi-data
"
}
export def "main list" [
--infra (-i): string = ""
--out: string = ""
]: nothing -> nothing {
if ($out | is-not-empty) {
set-provisioning-out $out
set-provisioning-no-terminal true
}
let res = (do { ^hcloud volume list -o json } | complete)
if $res.exit_code != 0 or ($res.stdout | str trim | is-empty) {
_print "⚠ hcloud unavailable or no volumes found"
return
}
let vols = ($res.stdout | from json)
if ($vols | is-empty) {
_print "No volumes found"
return
}
# Resolve infra filter from workspace context
let infra_filter = if ($infra | is-not-empty) { $infra | path basename } else {
let ws_config = ($env.PWD | path join "config" "provisioning.ncl")
if ($ws_config | path exists) {
(ncl-eval-soft $ws_config [] {} | get -o current_infra | default "")
} else { "" }
}
let rows = ($vols | each {|v|
let server_name = ($v.server?.name? | default "—")
let protection = if ($v.protection?.delete? | default false) { "🔒" } else { "" }
{
name: $v.name
size: $"($v.size)GB"
location: ($v.location?.name? | default "")
format: ($v.format? | default "—")
server: $server_name
status: $v.status
protection: $protection
}
})
_print ($rows | table -i false)
}
export def "main create" [
name: string
--size (-s): int = 20
--location (-l): string = ""
--format (-f): string = "ext4"
--yes (-y)
]: nothing -> nothing {
if ($name | is-empty) {
error make { msg: "Usage: provisioning volume create <name> [--size <GB>] [--location <loc>]" }
}
# Resolve location: flag > infra settings > fsn1
let loc = if ($location | is-not-empty) { $location } else {
let ws_config = ($env.PWD | path join "config" "provisioning.ncl")
if ($ws_config | path exists) {
(ncl-eval-soft $ws_config [] {} | get -o region | default "fsn1")
} else { "fsn1" }
}
# Check if already exists
let existing = (do { ^hcloud volume describe $name -o json } | complete)
if $existing.exit_code == 0 {
_print $" Volume '($name)' already exists"
return
}
if not $yes {
_print $"Create volume '($name)' — ($size)GB, ($loc), format: ($format)"
_print "Confirm? [y/N] "
let c = (input "")
if $c not-in ["y", "Y", "yes"] { _print "Aborted."; return }
}
let res = (do { ^hcloud volume create --name $name --size ($size | into string) --location $loc --format $format } | complete)
if $res.exit_code != 0 {
error make { msg: $"Failed to create volume: ($res.stderr)" }
}
_print $"✓ Volume '($name)' created — ($size)GB at ($loc)"
}
export def "main attach" [
name: string
--server (-s): string = ""
--yes (-y)
]: nothing -> nothing {
if ($name | is-empty) or ($server | is-empty) {
error make { msg: "Usage: provisioning volume attach <name> --server <hostname>" }
}
let vol_res = (do { ^hcloud volume describe $name -o json } | complete)
if $vol_res.exit_code != 0 {
error make { msg: $"Volume '($name)' not found" }
}
let vol = ($vol_res.stdout | from json)
let current_srv = ($vol.server?.name? | default "")
if ($current_srv | is-not-empty) {
if $current_srv == $server {
_print $" Volume '($name)' already attached to '($server)'"
return
}
error make { msg: $"Volume '($name)' is attached to '($current_srv)' — detach first" }
}
let res = (do { ^hcloud volume attach $name --server $server } | complete)
if $res.exit_code != 0 {
error make { msg: $"Failed to attach: ($res.stderr)" }
}
_print $"✓ Volume '($name)' attached to '($server)'"
}
export def "main detach" [
name: string
--yes (-y)
]: nothing -> nothing {
if ($name | is-empty) {
error make { msg: "Usage: provisioning volume detach <name>" }
}
let vol_res = (do { ^hcloud volume describe $name -o json } | complete)
if $vol_res.exit_code != 0 {
error make { msg: $"Volume '($name)' not found" }
}
let vol = ($vol_res.stdout | from json)
let current_srv = ($vol.server?.name? | default "")
if ($current_srv | is-empty) {
_print $" Volume '($name)' is not attached"
return
}
if not $yes {
_print $"Detach '($name)' from '($current_srv)'? [y/N] "
let c = (input "")
if $c not-in ["y", "Y", "yes"] { _print "Aborted."; return }
}
let res = (do { ^hcloud volume detach $name } | complete)
if $res.exit_code != 0 {
error make { msg: $"Failed to detach: ($res.stderr)" }
}
_print $"✓ Volume '($name)' detached from '($current_srv)'"
}
export def "main delete" [
name: string
--yes (-y)
]: nothing -> nothing {
if ($name | is-empty) {
error make { msg: "Usage: provisioning volume delete <name>" }
}
let vol_res = (do { ^hcloud volume describe $name -o json } | complete)
if $vol_res.exit_code != 0 {
error make { msg: $"Volume '($name)' not found" }
}
let vol = ($vol_res.stdout | from json)
if ($vol.server? | default null) != null {
error make { msg: $"Volume '($name)' is attached to '($vol.server.name)' — detach first" }
}
if not $yes {
_print $"Permanently delete volume '($name)' (($vol.size)GB)? Type '($name)' to confirm: "
let c = (input "")
if $c != $name { _print "Aborted."; return }
}
# Disable protection if set
if ($vol.protection?.delete? | default false) {
let unlock = (do { ^hcloud volume disable-protection $name delete } | complete)
if $unlock.exit_code != 0 {
error make { msg: $"Failed to disable protection: ($unlock.stderr)" }
}
}
let res = (do { ^hcloud volume delete $name } | complete)
if $res.exit_code != 0 {
error make { msg: $"Failed to delete: ($res.stderr)" }
}
_print $"✓ Volume '($name)' deleted"
}