Enhance ISO selection logic and update documentation for improved clarity and usage instructions
This commit is contained in:
parent
b6886cb34a
commit
9a7bd81d17
12 changed files with 287 additions and 16 deletions
47
.env.example
47
.env.example
|
|
@ -1,14 +1,61 @@
|
||||||
|
# Base Proxmox endpoint, including scheme and port.
|
||||||
|
# Required for live API access.
|
||||||
PROXMOX_URL=https://proxmox.example.invalid:8006
|
PROXMOX_URL=https://proxmox.example.invalid:8006
|
||||||
|
|
||||||
|
# Login realm used for authentication.
|
||||||
|
# Required for live API access.
|
||||||
PROXMOX_REALM=pam
|
PROXMOX_REALM=pam
|
||||||
|
|
||||||
|
# Proxmox username. If it does not already include @realm, the app appends PROXMOX_REALM.
|
||||||
|
# Required for live API access.
|
||||||
PROXMOX_USER=root
|
PROXMOX_USER=root
|
||||||
|
|
||||||
|
# Password for the configured user.
|
||||||
|
# Required for live API access.
|
||||||
PROXMOX_PASSWORD=replace-me
|
PROXMOX_PASSWORD=replace-me
|
||||||
|
|
||||||
|
# Verify TLS certificates for API requests.
|
||||||
|
# Recommended: true for trusted certificates, false only for known self-signed/internal setups.
|
||||||
PROXMOX_VERIFY_TLS=false
|
PROXMOX_VERIFY_TLS=false
|
||||||
|
|
||||||
|
# Usually leave this at the default Proxmox API base.
|
||||||
PROXMOX_API_BASE=/api2/json
|
PROXMOX_API_BASE=/api2/json
|
||||||
|
|
||||||
|
# Optional ISO auto-selection rule for the OS step.
|
||||||
|
# Uses glob syntax by default. Prefix with "regex:" to use a regular expression.
|
||||||
|
# Examples:
|
||||||
|
# PROXMOX_DEFAULT_ISO_SELECTOR=*ubuntu*
|
||||||
|
# PROXMOX_DEFAULT_ISO_SELECTOR=regex:nixos-minimal-\d{2}\.\d{2}\..*-x86_64-linux\.iso$
|
||||||
|
PROXMOX_DEFAULT_ISO_SELECTOR=
|
||||||
|
|
||||||
|
# Global create safety switch.
|
||||||
|
# false = allows creates
|
||||||
|
# true = blocks creates
|
||||||
PROXMOX_PREVENT_CREATE=false
|
PROXMOX_PREVENT_CREATE=false
|
||||||
|
|
||||||
|
# Restrict live creates to a dedicated test scope.
|
||||||
|
# When true, the PROXMOX_TEST_* values below become required.
|
||||||
PROXMOX_ENABLE_TEST_MODE=false
|
PROXMOX_ENABLE_TEST_MODE=false
|
||||||
|
|
||||||
|
# Required only when PROXMOX_ENABLE_TEST_MODE=true.
|
||||||
|
# Creates are restricted to this node.
|
||||||
PROXMOX_TEST_NODE=
|
PROXMOX_TEST_NODE=
|
||||||
|
|
||||||
|
# Required only when PROXMOX_ENABLE_TEST_MODE=true.
|
||||||
|
# Creates are restricted to this resource pool.
|
||||||
PROXMOX_TEST_POOL=
|
PROXMOX_TEST_POOL=
|
||||||
|
|
||||||
|
# Required only when PROXMOX_ENABLE_TEST_MODE=true.
|
||||||
|
# Automatically added to created VMs in test mode.
|
||||||
PROXMOX_TEST_TAG=codex-e2e
|
PROXMOX_TEST_TAG=codex-e2e
|
||||||
|
|
||||||
|
# Required only when PROXMOX_ENABLE_TEST_MODE=true.
|
||||||
|
# Automatically prefixed to VM names in test mode.
|
||||||
PROXMOX_TEST_VM_NAME_PREFIX=codex-e2e-
|
PROXMOX_TEST_VM_NAME_PREFIX=codex-e2e-
|
||||||
|
|
||||||
|
# Reserved for future failed-create cleanup behavior.
|
||||||
|
# Parsed today, but not yet acted on by the workflow.
|
||||||
PROXMOX_KEEP_FAILED_VM=true
|
PROXMOX_KEEP_FAILED_VM=true
|
||||||
|
|
||||||
|
# Request timeout used for API calls and task polling.
|
||||||
PROXMOX_REQUEST_TIMEOUT_SECONDS=15
|
PROXMOX_REQUEST_TIMEOUT_SECONDS=15
|
||||||
|
|
|
||||||
172
README.md
172
README.md
|
|
@ -1,25 +1,173 @@
|
||||||
|
# pve-vm-setup
|
||||||
|
|
||||||
|
Textual TUI for creating Proxmox VMs with live reference data, guarded create controls, and a guided multi-step wizard.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
- logs into a Proxmox VE API endpoint
|
||||||
|
- loads nodes, pools, storages, bridges, tags, and ISO images from the live cluster
|
||||||
|
- walks through VM creation in a step-by-step wizard
|
||||||
|
- can run in a safe read-only mode, a normal live-create mode, or a restricted test mode
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
- Install: `uv sync`
|
- Run directly from this repo (without cloning): `uvx git+https://git.s1q.dev/phg/pve-vm-setup.git`
|
||||||
- Run app: `uv run python -m pve_vm_setup`
|
- Install dependencies: `uv sync`
|
||||||
- Run live diagnostics: `uv run python -m pve_vm_setup --doctor-live`
|
- Run the app from the checkout repository: `uv run -m pve_vm_setup`
|
||||||
|
- Run live diagnostics: `uv run -m pve_vm_setup --doctor-live`
|
||||||
- Run tests: `uv run pytest`
|
- Run tests: `uv run pytest`
|
||||||
- Run read-only live tests: `uv run pytest -m live`
|
- Run read-only live tests: `uv run pytest -m live`
|
||||||
- Run create-gated live tests: `uv run pytest -m live_create`
|
- Run live create tests: `uv run pytest -m live_create`
|
||||||
- Lint: `uv run ruff check .`
|
- Lint: `uv run ruff check .`
|
||||||
- Format: `uv run ruff format .`
|
- Format: `uv run ruff format .`
|
||||||
|
|
||||||
## Live configuration
|
## Typical usage
|
||||||
|
|
||||||
Start from `.env.example` and provide the Proxmox credentials in `.env`.
|
1. Copy `.env.example` to `.env`.
|
||||||
|
2. Fill in the Proxmox connection settings.
|
||||||
|
3. Decide whether this machine should be allowed to create VMs at all.
|
||||||
|
4. Optionally enable test mode if you want live creates restricted to a known node/pool and auto-tagged.
|
||||||
|
5. Start the app with `uv run python -m pve_vm_setup`.
|
||||||
|
6. Log in and complete the wizard.
|
||||||
|
|
||||||
Additional live-access controls:
|
Before the final create request, the app asks whether the VM should be started automatically after creation.
|
||||||
|
|
||||||
- `PROXMOX_VERIFY_TLS=false` disables certificate verification for internal/self-signed installs
|
## Operating modes
|
||||||
- `PROXMOX_API_BASE=/api2/json` makes the API base explicit
|
|
||||||
- `PROXMOX_PREVENT_CREATE=false` allows VM creation by default; set it to `true` to block creates
|
### Read-only mode
|
||||||
- `PROXMOX_ENABLE_TEST_MODE=true` enables scoped test mode for live creates
|
|
||||||
- When test mode is enabled, `PROXMOX_TEST_NODE`, `PROXMOX_TEST_POOL`, `PROXMOX_TEST_TAG`, and `PROXMOX_TEST_VM_NAME_PREFIX` are required and are used to constrain and mark created VMs
|
Use this when you want to browse live data and validate the setup without creating anything.
|
||||||
|
|
||||||
|
- Set `PROXMOX_PREVENT_CREATE=true`
|
||||||
|
- Recommended for first-time setup
|
||||||
|
- `--doctor-live` is useful here
|
||||||
|
|
||||||
|
### Normal live-create mode
|
||||||
|
|
||||||
|
Use this when you want to create real VMs without the extra test-mode restrictions.
|
||||||
|
|
||||||
|
- Set `PROXMOX_PREVENT_CREATE=false` or leave it unset
|
||||||
|
- Leave `PROXMOX_ENABLE_TEST_MODE=false`
|
||||||
|
- Recommended only when you are comfortable with the target cluster and defaults
|
||||||
|
|
||||||
|
### Restricted test mode
|
||||||
|
|
||||||
|
Use this when you want live creates, but only inside a constrained sandbox.
|
||||||
|
|
||||||
|
- Set `PROXMOX_PREVENT_CREATE=false`
|
||||||
|
- Set `PROXMOX_ENABLE_TEST_MODE=true`
|
||||||
|
- `PROXMOX_TEST_NODE`, `PROXMOX_TEST_POOL`, `PROXMOX_TEST_TAG`, and `PROXMOX_TEST_VM_NAME_PREFIX` become required
|
||||||
|
- The app restricts creates to the configured node and pool
|
||||||
|
- The app automatically adds the configured tag and name prefix
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
Start from `.env.example`. The table below describes every supported variable that is currently parsed by the app.
|
||||||
|
|
||||||
|
| Variable | Required | Default | Recommended | Purpose |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `PROXMOX_URL` | Required for live access | none | Yes | Base Proxmox URL, for example `https://pve.example.com:8006`. |
|
||||||
|
| `PROXMOX_REALM` | Required for live access | none | Yes | Proxmox auth realm, for example `pam`, `pve`, or `ldap`. |
|
||||||
|
| `PROXMOX_USER` | Required for live access | none | Yes | Username used for API login. If it does not contain `@realm`, the configured realm is appended automatically. |
|
||||||
|
| `PROXMOX_PASSWORD` | Required for live access | none | Yes | Password for the Proxmox user. |
|
||||||
|
| `PROXMOX_VERIFY_TLS` | Optional | `false` | Yes, if your certificates are valid | Controls TLS certificate verification for API calls. Set to `true` for properly trusted certificates. Set to `false` only for internal or self-signed setups you explicitly trust. |
|
||||||
|
| `PROXMOX_API_BASE` | Optional | `/api2/json` | Usually leave as-is | API base path appended to `PROXMOX_URL`. Only change this if your deployment needs a different base path. |
|
||||||
|
| `PROXMOX_REQUEST_TIMEOUT_SECONDS` | Optional | `15` | Usually yes | Request timeout used for API calls and task polling. Increase it if your environment is slow. |
|
||||||
|
| `PROXMOX_DEFAULT_ISO_SELECTOR` | Optional | unset | Optional | Controls which ISO image is auto-selected in the OS step. Uses glob matching by default. If prefixed with `regex:`, the remainder is treated as a regular expression. |
|
||||||
|
| `PROXMOX_PREVENT_CREATE` | Optional | `false` | Yes | Global create safety switch. Set to `true` to block VM creation completely. Leave unset or set to `false` to allow creates. |
|
||||||
|
| `PROXMOX_ENABLE_TEST_MODE` | Optional | `false` | Yes for shared or risky environments | Enables restricted live-create mode. When enabled, the `PROXMOX_TEST_*` scope settings become mandatory. |
|
||||||
|
| `PROXMOX_TEST_NODE` | Required only in test mode | none | Yes in test mode | Node that live creates are restricted to. |
|
||||||
|
| `PROXMOX_TEST_POOL` | Required only in test mode | none | Yes in test mode | Pool that live creates are restricted to. |
|
||||||
|
| `PROXMOX_TEST_TAG` | Required only in test mode | `codex-e2e` | Yes in test mode | Tag added automatically to created VMs in test mode. |
|
||||||
|
| `PROXMOX_TEST_VM_NAME_PREFIX` | Required only in test mode | `codex-e2e-` | Yes in test mode | Prefix added automatically to VM names in test mode. |
|
||||||
|
| `PROXMOX_KEEP_FAILED_VM` | Optional | `true` | Leave as-is for now | Parsed by settings, but currently not acted on by the create workflow yet. Treat it as reserved for future cleanup behavior. |
|
||||||
|
|
||||||
|
## ISO selector syntax
|
||||||
|
|
||||||
|
`PROXMOX_DEFAULT_ISO_SELECTOR` supports two forms:
|
||||||
|
|
||||||
|
- Glob syntax, used by default
|
||||||
|
- Regex syntax, enabled with a `regex:` prefix
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- `PROXMOX_DEFAULT_ISO_SELECTOR=*ubuntu*`
|
||||||
|
- `PROXMOX_DEFAULT_ISO_SELECTOR=*debian-12*`
|
||||||
|
- `PROXMOX_DEFAULT_ISO_SELECTOR=regex:nixos-minimal-\d{2}\.\d{2}\..*-x86_64-linux\.iso$`
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
|
||||||
|
- if the selector matches one or more ISOs, the app picks from those matches
|
||||||
|
- if multiple matching NixOS-style ISOs exist, it prefers the latest one by release naming
|
||||||
|
- if nothing matches, the app falls back to the built-in default picker
|
||||||
|
|
||||||
|
## Recommended `.env` setups
|
||||||
|
|
||||||
|
### Safe initial setup
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
PROXMOX_URL=https://pve.example.com:8006
|
||||||
|
PROXMOX_REALM=pam
|
||||||
|
PROXMOX_USER=root
|
||||||
|
PROXMOX_PASSWORD=replace-me
|
||||||
|
PROXMOX_VERIFY_TLS=true
|
||||||
|
PROXMOX_PREVENT_CREATE=true
|
||||||
|
PROXMOX_ENABLE_TEST_MODE=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Normal live-create setup
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
PROXMOX_URL=https://pve.example.com:8006
|
||||||
|
PROXMOX_REALM=pam
|
||||||
|
PROXMOX_USER=root
|
||||||
|
PROXMOX_PASSWORD=replace-me
|
||||||
|
PROXMOX_VERIFY_TLS=true
|
||||||
|
PROXMOX_PREVENT_CREATE=false
|
||||||
|
PROXMOX_ENABLE_TEST_MODE=false
|
||||||
|
PROXMOX_DEFAULT_ISO_SELECTOR=*nixos*
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restricted test setup
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
PROXMOX_URL=https://pve.example.com:8006
|
||||||
|
PROXMOX_REALM=pam
|
||||||
|
PROXMOX_USER=root
|
||||||
|
PROXMOX_PASSWORD=replace-me
|
||||||
|
PROXMOX_VERIFY_TLS=true
|
||||||
|
PROXMOX_PREVENT_CREATE=false
|
||||||
|
PROXMOX_ENABLE_TEST_MODE=true
|
||||||
|
PROXMOX_TEST_NODE=pve-test-01
|
||||||
|
PROXMOX_TEST_POOL=sandbox
|
||||||
|
PROXMOX_TEST_TAG=codex-e2e
|
||||||
|
PROXMOX_TEST_VM_NAME_PREFIX=codex-e2e-
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes and caveats
|
||||||
|
|
||||||
|
- Live access requires `PROXMOX_URL`, `PROXMOX_USER`, `PROXMOX_PASSWORD`, and `PROXMOX_REALM`.
|
||||||
|
- Test mode is not the same as read-only mode. Test mode still performs real creates when creation is allowed.
|
||||||
|
- If `PROXMOX_PREVENT_CREATE=true`, the confirm step validates but actual creation is blocked.
|
||||||
|
- `PROXMOX_TEST_POOL` is currently required when test mode is enabled.
|
||||||
|
- `PROXMOX_KEEP_FAILED_VM` is currently reserved and not yet implemented in the workflow logic.
|
||||||
|
|
||||||
|
## Live diagnostics
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python -m pve_vm_setup --doctor-live
|
||||||
|
```
|
||||||
|
|
||||||
|
This verifies:
|
||||||
|
|
||||||
|
- transport reachability
|
||||||
|
- API base access
|
||||||
|
- visible nodes
|
||||||
|
- configured test node and pool, when test mode is enabled
|
||||||
|
|
||||||
|
Use this before enabling creates against a real cluster.
|
||||||
|
|
||||||
## Engineering rules
|
## Engineering rules
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import fnmatch
|
||||||
import re
|
import re
|
||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
|
|
||||||
|
|
@ -10,6 +11,7 @@ from .settings import AppSettings
|
||||||
_NIXOS_ISO_PATTERN = re.compile(
|
_NIXOS_ISO_PATTERN = re.compile(
|
||||||
r"nixos-minimal-(?P<year>\d{2})[.-](?P<month>\d{2})\.[A-Za-z0-9]+-[A-Za-z0-9_]+-linux\.iso$"
|
r"nixos-minimal-(?P<year>\d{2})[.-](?P<month>\d{2})\.[A-Za-z0-9]+-[A-Za-z0-9_]+-linux\.iso$"
|
||||||
)
|
)
|
||||||
|
_REGEX_SELECTOR_PREFIX = "regex:"
|
||||||
|
|
||||||
|
|
||||||
def select_latest_nixos_iso(isos: list[str]) -> str | None:
|
def select_latest_nixos_iso(isos: list[str]) -> str | None:
|
||||||
|
|
@ -23,6 +25,21 @@ def select_latest_nixos_iso(isos: list[str]) -> str | None:
|
||||||
return max(candidates)[2]
|
return max(candidates)[2]
|
||||||
|
|
||||||
|
|
||||||
|
def _matches_iso_selector(iso: str, selector: str) -> bool:
|
||||||
|
if selector.startswith(_REGEX_SELECTOR_PREFIX):
|
||||||
|
pattern = selector.removeprefix(_REGEX_SELECTOR_PREFIX)
|
||||||
|
return re.search(pattern, iso) is not None
|
||||||
|
return fnmatch.fnmatch(iso, selector)
|
||||||
|
|
||||||
|
|
||||||
|
def select_preferred_iso(isos: list[str], selector: str | None = None) -> str | None:
|
||||||
|
if selector:
|
||||||
|
matches = sorted(iso for iso in isos if _matches_iso_selector(iso, selector))
|
||||||
|
if matches:
|
||||||
|
return select_latest_nixos_iso(matches) or matches[0]
|
||||||
|
return select_latest_nixos_iso(isos)
|
||||||
|
|
||||||
|
|
||||||
def build_startup_value(order: str, up: str, down: str) -> str:
|
def build_startup_value(order: str, up: str, down: str) -> str:
|
||||||
parts: list[str] = []
|
parts: list[str] = []
|
||||||
if order.strip():
|
if order.strip():
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -12,7 +12,7 @@ from textual.widgets import Button, Checkbox, Input, Select, Static
|
||||||
|
|
||||||
from ..domain import (
|
from ..domain import (
|
||||||
build_confirmation_text,
|
build_confirmation_text,
|
||||||
select_latest_nixos_iso,
|
select_preferred_iso,
|
||||||
validate_all_steps,
|
validate_all_steps,
|
||||||
validate_step,
|
validate_step,
|
||||||
)
|
)
|
||||||
|
|
@ -55,10 +55,13 @@ class AutoStartConfirmModal(ModalScreen[bool | None]):
|
||||||
#auto-start-actions {
|
#auto-start-actions {
|
||||||
margin-top: 1;
|
margin-top: 1;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
width: 1fr;
|
||||||
|
align-horizontal: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#auto-start-actions Button {
|
#auto-start-actions Button {
|
||||||
min-width: 8;
|
width: 12;
|
||||||
|
min-width: 12;
|
||||||
margin-right: 1;
|
margin-right: 1;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
@ -676,7 +679,10 @@ class WizardView(Vertical):
|
||||||
self._workflow.reference_data.isos = iso_values
|
self._workflow.reference_data.isos = iso_values
|
||||||
self._loaded_iso_source = (node, storage)
|
self._loaded_iso_source = (node, storage)
|
||||||
self._set_select_options("os-iso", iso_values)
|
self._set_select_options("os-iso", iso_values)
|
||||||
preferred = select_latest_nixos_iso(iso_values) or (iso_values[0] if iso_values else "")
|
preferred = select_preferred_iso(
|
||||||
|
iso_values,
|
||||||
|
self._settings.default_iso_selector,
|
||||||
|
) or (iso_values[0] if iso_values else "")
|
||||||
if preferred:
|
if preferred:
|
||||||
self.query_one("#os-iso", Select).value = preferred
|
self.query_one("#os-iso", Select).value = preferred
|
||||||
self._workflow.config.os.iso = preferred
|
self._workflow.config.os.iso = preferred
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -82,6 +83,7 @@ class AppSettings:
|
||||||
proxmox_realm: str | None
|
proxmox_realm: str | None
|
||||||
proxmox_verify_tls: bool
|
proxmox_verify_tls: bool
|
||||||
request_timeout_seconds: int
|
request_timeout_seconds: int
|
||||||
|
default_iso_selector: str | None
|
||||||
safety_policy: LiveSafetyPolicy
|
safety_policy: LiveSafetyPolicy
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -122,6 +124,14 @@ class AppSettings:
|
||||||
proxmox_url = (raw.get("PROXMOX_URL") or "").strip() or None
|
proxmox_url = (raw.get("PROXMOX_URL") or "").strip() or None
|
||||||
if proxmox_url is not None:
|
if proxmox_url is not None:
|
||||||
proxmox_url = proxmox_url.rstrip("/")
|
proxmox_url = proxmox_url.rstrip("/")
|
||||||
|
default_iso_selector = (raw.get("PROXMOX_DEFAULT_ISO_SELECTOR") or "").strip() or None
|
||||||
|
if default_iso_selector and default_iso_selector.startswith("regex:"):
|
||||||
|
try:
|
||||||
|
re.compile(default_iso_selector.removeprefix("regex:"))
|
||||||
|
except re.error as exc:
|
||||||
|
raise SettingsError(
|
||||||
|
"Invalid PROXMOX_DEFAULT_ISO_SELECTOR regex."
|
||||||
|
) from exc
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
proxmox_url=proxmox_url,
|
proxmox_url=proxmox_url,
|
||||||
|
|
@ -133,6 +143,7 @@ class AppSettings:
|
||||||
request_timeout_seconds=_parse_int(
|
request_timeout_seconds=_parse_int(
|
||||||
raw.get("PROXMOX_REQUEST_TIMEOUT_SECONDS"), default=15
|
raw.get("PROXMOX_REQUEST_TIMEOUT_SECONDS"), default=15
|
||||||
),
|
),
|
||||||
|
default_iso_selector=default_iso_selector,
|
||||||
safety_policy=safety_policy,
|
safety_policy=safety_policy,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,4 +1,9 @@
|
||||||
from pve_vm_setup.domain import build_create_payload, select_latest_nixos_iso, validate_all_steps
|
from pve_vm_setup.domain import (
|
||||||
|
build_create_payload,
|
||||||
|
select_latest_nixos_iso,
|
||||||
|
select_preferred_iso,
|
||||||
|
validate_all_steps,
|
||||||
|
)
|
||||||
from pve_vm_setup.models.workflow import VmConfig
|
from pve_vm_setup.models.workflow import VmConfig
|
||||||
from pve_vm_setup.settings import AppSettings
|
from pve_vm_setup.settings import AppSettings
|
||||||
|
|
||||||
|
|
@ -15,6 +20,32 @@ def test_select_latest_nixos_iso_prefers_latest_year_month() -> None:
|
||||||
assert choice == "cephfs:iso/nixos-minimal-25.05.ffffeeee-x86_64-linux.iso"
|
assert choice == "cephfs:iso/nixos-minimal-25.05.ffffeeee-x86_64-linux.iso"
|
||||||
|
|
||||||
|
|
||||||
|
def test_select_preferred_iso_uses_glob_selector_when_configured() -> None:
|
||||||
|
choice = select_preferred_iso(
|
||||||
|
[
|
||||||
|
"cephfs:iso/debian-12.iso",
|
||||||
|
"cephfs:iso/nixos-minimal-24.11.1234abcd-x86_64-linux.iso",
|
||||||
|
"cephfs:iso/ubuntu-24.04.iso",
|
||||||
|
],
|
||||||
|
"*ubuntu*",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert choice == "cephfs:iso/ubuntu-24.04.iso"
|
||||||
|
|
||||||
|
|
||||||
|
def test_select_preferred_iso_uses_regex_selector_when_prefixed() -> None:
|
||||||
|
choice = select_preferred_iso(
|
||||||
|
[
|
||||||
|
"cephfs:iso/debian-12.iso",
|
||||||
|
"cephfs:iso/nixos-minimal-24.11.1234abcd-x86_64-linux.iso",
|
||||||
|
"cephfs:iso/nixos-graphical-25.05.iso",
|
||||||
|
],
|
||||||
|
r"regex:nixos-graphical-\d{2}\.\d{2}\.iso$",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert choice == "cephfs:iso/nixos-graphical-25.05.iso"
|
||||||
|
|
||||||
|
|
||||||
def test_build_create_payload_applies_safety_name_tag_and_key_settings() -> None:
|
def test_build_create_payload_applies_safety_name_tag_and_key_settings() -> None:
|
||||||
settings = AppSettings.from_env(
|
settings = AppSettings.from_env(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ def test_settings_load_defaults_and_normalize_api_base() -> None:
|
||||||
assert settings.proxmox_api_base == "/api2/json"
|
assert settings.proxmox_api_base == "/api2/json"
|
||||||
assert settings.proxmox_verify_tls is False
|
assert settings.proxmox_verify_tls is False
|
||||||
assert settings.request_timeout_seconds == 15
|
assert settings.request_timeout_seconds == 15
|
||||||
|
assert settings.default_iso_selector is None
|
||||||
assert settings.effective_username == "root@pam"
|
assert settings.effective_username == "root@pam"
|
||||||
assert settings.safety_policy.prevent_create is False
|
assert settings.safety_policy.prevent_create is False
|
||||||
assert settings.safety_policy.enable_test_mode is False
|
assert settings.safety_policy.enable_test_mode is False
|
||||||
|
|
@ -54,3 +55,13 @@ def test_settings_allow_create_by_default_when_prevent_flag_is_unset() -> None:
|
||||||
|
|
||||||
assert settings.safety_policy.prevent_create is False
|
assert settings.safety_policy.prevent_create is False
|
||||||
assert settings.safety_policy.allow_create is True
|
assert settings.safety_policy.allow_create is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_settings_reject_invalid_default_iso_regex_selector() -> None:
|
||||||
|
with pytest.raises(SettingsError):
|
||||||
|
AppSettings.from_env(
|
||||||
|
{
|
||||||
|
"PROXMOX_DEFAULT_ISO_SELECTOR": "regex:[unterminated",
|
||||||
|
},
|
||||||
|
load_dotenv_file=False,
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue