Fix exit code bugs, doctor output, and labs parsing from live testing
Tested all new commands (--resources, --labs, --lab-down, --lab-up, --doctor, --status) against live Proxmox with 4 running IncusOS VMs. Fixes: - return → return 0 in 4 early-exit paths (pipefail + [[ ]] false propagated non-zero exit codes) - do_list_labs: broken ERE regex [^\ \]]+ → [^\ ]+ for config tag - do_list_labs: line-based Python output instead of delimiter-based tr (descriptions with spaces broke read parsing) - do_list_labs: unsafe echo|tr|sort pipeline → printf|sort - run_doctor: operations-center version → --version (shows 0.2.2) - run_doctor: ls glob pipeline crash under pipefail (|| true) CLAUDE.md: - Document env file (PROXMOX_TOKEN_SECRET) and source workflow - Add Sys.Audit to minimum API privileges (needed for --resources) - Correct role name to IncusOSDeployer - Add /nodes/pve ACL requirement for node-level endpoints Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f812e04746
commit
37d63c35a6
13
CLAUDE.md
13
CLAUDE.md
|
|
@ -20,6 +20,7 @@ incu-contrib/
|
||||||
│ ├── incusos-proxmox # Declarative Proxmox VM deployment + lab lifecycle
|
│ ├── incusos-proxmox # Declarative Proxmox VM deployment + lab lifecycle
|
||||||
│ ├── lab-test # Guided lab validation (12 test phases)
|
│ ├── lab-test # Guided lab validation (12 test phases)
|
||||||
│ ├── proxmox.yaml # Proxmox connection config (gitignored, contains credentials)
|
│ ├── proxmox.yaml # Proxmox connection config (gitignored, contains credentials)
|
||||||
|
│ ├── ../env # PROXMOX_TOKEN_SECRET (gitignored); source with: source env
|
||||||
│ ├── TESTING.md # Testing guide for incusos-proxmox and lab-test
|
│ ├── TESTING.md # Testing guide for incusos-proxmox and lab-test
|
||||||
│ └── examples/ # Example seed + Proxmox YAML files
|
│ └── examples/ # Example seed + Proxmox YAML files
|
||||||
└── notes/ # Research notes and reference material
|
└── notes/ # Research notes and reference material
|
||||||
|
|
@ -88,15 +89,20 @@ incu-contrib/
|
||||||
etc.) only define VM specs. Merge priority: `proxmox.yaml` base → lab config
|
etc.) only define VM specs. Merge priority: `proxmox.yaml` base → lab config
|
||||||
`proxmox:` overlay → CLI flags (`--host`, `--method`). Auto-discovery looks
|
`proxmox:` overlay → CLI flags (`--host`, `--method`). Auto-discovery looks
|
||||||
for `proxmox.yaml` in script directory then cwd; override with `--proxmox FILE`.
|
for `proxmox.yaml` in script directory then cwd; override with `--proxmox FILE`.
|
||||||
|
- **API token secret**: stored in `env` file at the repo root (gitignored).
|
||||||
|
Source it before running scripts: `source env` (exports `PROXMOX_TOKEN_SECRET`).
|
||||||
- **Connection methods**: SSH (default, `ssh root@host qm ...`) or API
|
- **Connection methods**: SSH (default, `ssh root@host qm ...`) or API
|
||||||
(`curl -k https://host:8006/api2/json/...` with `PVEAPIToken` header).
|
(`curl -k https://host:8006/api2/json/...` with `PVEAPIToken` header).
|
||||||
- **Minimum API privileges** for token-based access:
|
- **Minimum API privileges** for token-based access (role: `IncusOSDeployer`):
|
||||||
```
|
```
|
||||||
VM.Allocate VM.Config.Disk VM.Config.CPU VM.Config.Memory
|
VM.Allocate VM.Config.Disk VM.Config.CPU VM.Config.Memory
|
||||||
VM.Config.Network VM.Config.CDROM VM.Config.Options VM.Config.HWType
|
VM.Config.Network VM.Config.CDROM VM.Config.Options VM.Config.HWType
|
||||||
VM.PowerMgmt VM.Audit Datastore.AllocateSpace Datastore.AllocateTemplate
|
VM.PowerMgmt VM.Audit Datastore.AllocateSpace Datastore.AllocateTemplate
|
||||||
Datastore.Audit SDN.Use
|
Datastore.Audit SDN.Use Sys.Audit
|
||||||
```
|
```
|
||||||
|
`Sys.Audit` is needed for `--resources` (host RAM/CPU/uptime via
|
||||||
|
`/nodes/<node>/status`). It requires a separate ACL on `/nodes/<node>`
|
||||||
|
since the pool-scoped ACL doesn't cover node-level endpoints.
|
||||||
- **Required VM settings** (getting any wrong causes IncusOS install failure):
|
- **Required VM settings** (getting any wrong causes IncusOS install failure):
|
||||||
- `bios=ovmf`, `machine=q35` -- UEFI boot required
|
- `bios=ovmf`, `machine=q35` -- UEFI boot required
|
||||||
- `efidisk0`: `pre-enrolled-keys=0` -- IncusOS enrolls its own Secure Boot keys
|
- `efidisk0`: `pre-enrolled-keys=0` -- IncusOS enrolls its own Secure Boot keys
|
||||||
|
|
@ -134,7 +140,8 @@ incu-contrib/
|
||||||
```
|
```
|
||||||
pveum pool add IncusLab --comment "IncusOS Lab VMs"
|
pveum pool add IncusLab --comment "IncusOS Lab VMs"
|
||||||
pveum pool modify IncusLab --storage local-lvm,local
|
pveum pool modify IncusLab --storage local-lvm,local
|
||||||
pveum aclmod /pool/IncusLab -user automation@pve -role IncusOSDeploy
|
pveum aclmod /pool/IncusLab -user automation@pve -role IncusOSDeployer
|
||||||
|
pveum aclmod /nodes/pve -user automation@pve -role IncusOSDeployer -propagate 0
|
||||||
```
|
```
|
||||||
- **`--status` command**: `incusos-proxmox --status config.yaml` shows per-VM
|
- **`--status` command**: `incusos-proxmox --status config.yaml` shows per-VM
|
||||||
deployment status (Proxmox state, install status, IP, port 8443, incus
|
deployment status (Proxmox state, install status, IP, port 8443, incus
|
||||||
|
|
|
||||||
|
|
@ -586,7 +586,7 @@ run_doctor() {
|
||||||
# operations-center CLI
|
# operations-center CLI
|
||||||
if command -v operations-center &>/dev/null; then
|
if command -v operations-center &>/dev/null; then
|
||||||
local oc_ver
|
local oc_ver
|
||||||
oc_ver=$(operations-center version 2>/dev/null | head -1 || echo "installed")
|
oc_ver=$(operations-center --version 2>/dev/null | head -1 || echo "installed")
|
||||||
echo -e " operations-center CLI: ${GREEN}${oc_ver} ✓${RESET}"
|
echo -e " operations-center CLI: ${GREEN}${oc_ver} ✓${RESET}"
|
||||||
else
|
else
|
||||||
echo -e " operations-center CLI: ${DIM}not installed (optional)${RESET}"
|
echo -e " operations-center CLI: ${DIM}not installed (optional)${RESET}"
|
||||||
|
|
@ -670,7 +670,7 @@ if versions:
|
||||||
local cache_base="${XDG_CACHE_HOME:-${HOME}/.cache}/${SCRIPT_NAME}/iso-cache"
|
local cache_base="${XDG_CACHE_HOME:-${HOME}/.cache}/${SCRIPT_NAME}/iso-cache"
|
||||||
if [[ -d "$cache_base" ]]; then
|
if [[ -d "$cache_base" ]]; then
|
||||||
local cached_iso
|
local cached_iso
|
||||||
cached_iso=$(ls -1 "${cache_base}"/IncusOS_*.iso 2>/dev/null | sort | tail -1)
|
cached_iso=$(ls -1 "${cache_base}"/IncusOS_*.iso 2>/dev/null | sort | tail -1 || true)
|
||||||
if [[ -n "$cached_iso" ]]; then
|
if [[ -n "$cached_iso" ]]; then
|
||||||
local cached_name
|
local cached_name
|
||||||
cached_name=$(basename "$cached_iso" .iso | sed 's/^IncusOS_//')
|
cached_name=$(basename "$cached_iso" .iso | sed 's/^IncusOS_//')
|
||||||
|
|
@ -3511,8 +3511,13 @@ for v in data:
|
||||||
local config_output
|
local config_output
|
||||||
config_output=$(pve_api GET "/nodes/${PVE_NODE}/qemu/${vmid}/config" 2>/dev/null) || continue
|
config_output=$(pve_api GET "/nodes/${PVE_NODE}/qemu/${vmid}/config" 2>/dev/null) || continue
|
||||||
|
|
||||||
local desc name mem_mib disk_gib status
|
local name="" mem_mib="" disk_gib="" desc="" status
|
||||||
read -r desc name mem_mib disk_gib < <(python3 -c "
|
{
|
||||||
|
read -r name
|
||||||
|
read -r mem_mib
|
||||||
|
read -r disk_gib
|
||||||
|
read -r desc
|
||||||
|
} < <(python3 -c "
|
||||||
import json
|
import json
|
||||||
data = json.loads('''${config_output}''').get('data', {})
|
data = json.loads('''${config_output}''').get('data', {})
|
||||||
desc = data.get('description', '')
|
desc = data.get('description', '')
|
||||||
|
|
@ -3526,8 +3531,11 @@ for part in scsi0.split(','):
|
||||||
sz = part.strip().split('=')[1]
|
sz = part.strip().split('=')[1]
|
||||||
if sz.endswith('G'):
|
if sz.endswith('G'):
|
||||||
disk_g = int(sz[:-1])
|
disk_g = int(sz[:-1])
|
||||||
print(f'{desc}|||{name}|||{mem}|||{disk_g}')
|
print(name)
|
||||||
" 2>/dev/null | tr '|||' ' ' || true)
|
print(mem)
|
||||||
|
print(disk_g)
|
||||||
|
print(desc)
|
||||||
|
" 2>/dev/null || true)
|
||||||
|
|
||||||
# Check for managed marker
|
# Check for managed marker
|
||||||
if [[ "$desc" != *"$MANAGED_MARKER"* ]]; then
|
if [[ "$desc" != *"$MANAGED_MARKER"* ]]; then
|
||||||
|
|
@ -3537,7 +3545,7 @@ print(f'{desc}|||{name}|||{mem}|||{disk_g}')
|
||||||
|
|
||||||
# Extract config= tag
|
# Extract config= tag
|
||||||
local config_tag="unknown"
|
local config_tag="unknown"
|
||||||
if [[ "$desc" =~ config=([^\ \]]+) ]]; then
|
if [[ "$desc" =~ config=([^\ ]+) ]]; then
|
||||||
config_tag="${BASH_REMATCH[1]}"
|
config_tag="${BASH_REMATCH[1]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -3556,7 +3564,9 @@ print(f'{desc}|||{name}|||{mem}|||{disk_g}')
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for config in $(echo "${!lab_configs[@]}" | tr ' ' '\n' | sort); do
|
local sorted_configs
|
||||||
|
sorted_configs=$(printf '%s\n' "${!lab_configs[@]}" | sort)
|
||||||
|
for config in $sorted_configs; do
|
||||||
local vms="${lab_configs[$config]}"
|
local vms="${lab_configs[$config]}"
|
||||||
local ram_mib="${lab_ram[$config]}"
|
local ram_mib="${lab_ram[$config]}"
|
||||||
local disk_gib="${lab_disk[$config]}"
|
local disk_gib="${lab_disk[$config]}"
|
||||||
|
|
@ -3592,6 +3602,7 @@ print(f'{desc}|||{name}|||{mem}|||{disk_g}')
|
||||||
done
|
done
|
||||||
|
|
||||||
[[ $unmanaged -gt 0 ]] && detail "${unmanaged} unmanaged VMs in pool"
|
[[ $unmanaged -gt 0 ]] && detail "${unmanaged} unmanaged VMs in pool"
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -4374,7 +4385,7 @@ main() {
|
||||||
|
|
||||||
[[ "$RESOURCES" == true ]] && do_resources
|
[[ "$RESOURCES" == true ]] && do_resources
|
||||||
[[ "$LIST_LABS" == true ]] && do_list_labs
|
[[ "$LIST_LABS" == true ]] && do_list_labs
|
||||||
return
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Handle cleanup-all without a config file (only needs proxmox.yaml)
|
# Handle cleanup-all without a config file (only needs proxmox.yaml)
|
||||||
|
|
@ -4463,7 +4474,7 @@ main() {
|
||||||
if [[ "$RESOURCES" == true ]] || [[ "$LIST_LABS" == true ]]; then
|
if [[ "$RESOURCES" == true ]] || [[ "$LIST_LABS" == true ]]; then
|
||||||
[[ "$RESOURCES" == true ]] && do_resources
|
[[ "$RESOURCES" == true ]] && do_resources
|
||||||
[[ "$LIST_LABS" == true ]] && do_list_labs
|
[[ "$LIST_LABS" == true ]] && do_list_labs
|
||||||
return
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Reconcile existing deployments (interactive menu on re-runs)
|
# Reconcile existing deployments (interactive menu on re-runs)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue