prvng_core/nulib/provisioning-volume.nu
Jesús Pérez 894046ef5a
feat(core): three-layer DAG, unified component arch, commands-registry cache, Nushell 0.112.2 migration
- DAG architecture: `dag show/validate/export` (nulib/main_provisioning/dag.nu),
    config loader (lib_provisioning/config/loader/dag.nu), taskserv dag-executor.
    Backed by schemas/lib/dag/*.ncl; orchestrator emits NATS events via
    WorkspaceComposition::into_workflow. See ADR-020, ADR-021.
  - Unified Component Architecture: components/mod.nu, main_provisioning/
    {components,workflow,extensions,ontoref-queries}.nu. Full workflow engine with
    topological sort and NATS subject emission. Blocks A-H complete (libre-daoshi).
  - Commands-registry: nulib/commands-registry.ncl (Nickel source, 314 lines) +
    JSON cache at ~/.cache/provisioning/commands-registry.json rebuilt on source
    change. cli/provisioning fast-path alias expansion avoids cold Nu startup.
    ADDING_COMMANDS.md documents new-command workflow.
  - Platform service manager: service-manager.nu (+573), startup.nu (+611),
    service-check.nu (+255); autostart/bootstrap/health/target refactored.
  - Nushell 0.112.2 migration: removed all try/catch and bash redirections;
    external commands prefixed with ^; type signatures enforced. Driven by
    scripts/refactor-try-catch{,-simplified}.nu.
  - TTY stack: removed shlib/*-tty.sh; replaced by cli/tty-dispatch.sh,
    tty-filter.sh, tty-commands.conf.
  - New domain modules: images/ (golden image lifecycle), workspace/{state,sync}.nu,
    main_provisioning/{bootstrap,cluster-deploy,fip,state}.nu, commands/{state,
    build,integrations/auth,utilities/alias}.nu, platform.nu expanded (+874).
  - Config loader overhaul: loader/core.nu slimmed (-759), cache/core.nu
    refactored (-454), removed legacy loaders/file_loader.nu (-330).
  - Thirteen new provisioning-<domain>.nu top-level modules for bash dispatcher.
  - Tests: test_workspace_state.nu (+351); updates to test_oci_registry,
    test_services.
  - README + CHANGELOG updated.
2026-04-17 04:27:33 +01:00

257 lines
8.4 KiB
Text
Raw Permalink 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"
}