proxmox: deployment files, sample, user guide and doc rows
The optional proxmox-token Secret (token, ca.pem) mounted at /etc/proxy-operator/proxmox, make proxmox-token-secret with content checks that never print the token, a commented proxmox-home entry in both providers configs, and config/samples/proxy_proxmox.yaml. TestLoadConfig_proxmoxSampleEntry loads the commented entry from both files, and a second test ties the sample Proxy's placement to it. docs/proxmox-provider.md is the user guide: prerequisites and privileges, cluster setup, config reference, placement, what lands on Proxmox, logging, tracing, timeouts, health check, error classes, known gaps and testing. providers-existing.md gains the proxmox column and section; architecture.md, the README, providers.md and CLAUDE.md gain their rows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ Pairs with [docs/plans/2026-09-18-1502-proxmox-provider.md](../plans/2026-09-18-
|
||||
- [x] Phase 6 — Create
|
||||
- [x] Phase 7 — Delete, Checker, CRD comment
|
||||
- [x] Phase 8 — Contract and observability sweep, integration test, testing.md
|
||||
- [ ] Phase 9 — Deployment and documentation
|
||||
- [x] Phase 9 — Deployment and documentation
|
||||
- [ ] Phase 10 — Review, real-host validation, changelog, MR
|
||||
|
||||
## Phase 0 — Plan file, branch, host recon, fixtures
|
||||
@@ -1121,3 +1121,180 @@ Worth noting:
|
||||
`go test -short ./...` run of this phase.
|
||||
- `.claude/settings.json` stays out of the commit. Nothing in this phase
|
||||
talked to the host.
|
||||
|
||||
## Phase 9 — Deployment and documentation
|
||||
|
||||
Deployment: the optional `proxmox-token` Secret (keys `token` and `ca.pem`)
|
||||
is mounted read-only at `/etc/proxy-operator/proxmox` in
|
||||
`config/manager/manager.yaml`, next to `kvm-ssh` and commented the same way.
|
||||
The Makefile gains `proxmox-token-secret`. Both providers configs gain a
|
||||
commented `proxmox-home` entry after kvm2's. `config/samples/proxy_proxmox.yaml`
|
||||
is listed in the samples kustomization. There is no RBAC change and no
|
||||
`hack/providers-dev.yaml` entry. Docs: `docs/proxmox-provider.md` follows
|
||||
kvm2's guide heading for heading and has no `—`. The rows and sections
|
||||
named below went into `providers-existing.md`, `architecture.md`, the
|
||||
README, `providers.md` and `CLAUDE.md`. `docs/testing.md` and the
|
||||
integration Makefile target were done in Phase 8 and were not touched; the
|
||||
guide's Testing section links to them.
|
||||
|
||||
Where the spec and the plan's Phase 9 text disagree, the spec won. The URL
|
||||
is `https://192.168.0.8:8006`, not 192.168.2.8, which is not in the node
|
||||
certificate. The Secret has two keys, and the target takes
|
||||
`PROXMOX_CA_FILE` as well as `PROXMOX_TOKEN_FILE`.
|
||||
|
||||
Judgment calls:
|
||||
|
||||
- **The target also checks both files' content.** The token file must be
|
||||
exactly one non-blank line matching
|
||||
`<user>@<realm>!<tokenid>=<secret>`. That is the shape `readToken`
|
||||
accepts: `TrimSpace`, then an anchored regexp. The check uses `grep -c`
|
||||
and `grep -qx`, so the secret is never printed. The CA check mirrors
|
||||
`kvm-ssh-secret`'s `PRIVATE KEY` check: the file must contain
|
||||
`BEGIN CERTIFICATE`. With `insecureSkipTLSVerify` the guide says to
|
||||
create the Secret by hand with the `token` key only.
|
||||
- **The sample-entry test covers both files.** `commentedEntry` finds the
|
||||
`# - name: proxmox-home` line and takes its indent. It keeps the lines
|
||||
that continue at `<indent># ` and strips `# ` at that indent. The same
|
||||
helper then works for the ConfigMap, whose entry sits six spaces deep
|
||||
inside a `|` block. So `TestLoadConfig_proxmoxSampleEntry` has a
|
||||
`sample` subtest and a `configmap` subtest. It checks every field the
|
||||
spec names, and that the optional fields stay commented out.
|
||||
- **A second test goes beyond the plan:**
|
||||
`TestLoadConfig_proxmoxSampleProxyMatchesSampleEntry`. The sample Proxy's
|
||||
provider, zone, machine type and image must resolve in the sample
|
||||
entry. kvm has exactly this bug as a known gap (`proxy_kvm.yaml` places
|
||||
on `nas1`).
|
||||
- **The sample entry also has `medium: {cores: 2, memoryMiB: 2048,
|
||||
diskGiB: 10}`**, next to the `small` the spec asks for, so `diskGiB`
|
||||
appears somewhere. The optional fields are commented out with a short
|
||||
note each, because no live proxmox entry exists to explain them. The
|
||||
kvm2 entry can rely on kvm's.
|
||||
- **In the ConfigMap the proxmox entry is commented out**, although kvm2's
|
||||
is live there. It needs a host and a token.
|
||||
- **providers-existing.md needed more than the column and the section.**
|
||||
The intro now says five providers and links the guide. In the shared
|
||||
gaps, six sentences would otherwise be false, and each now names
|
||||
proxmox where it applies: adoption verified in two of the five, Create
|
||||
blocking in all three VM providers, no Terminated, the list policy, the
|
||||
collector gap, and the "kvm2 does this" in proposed features. The new
|
||||
section is §8, so the shared gaps and proposed features move to §9 and
|
||||
§10. No doc links those sections by anchor (grep).
|
||||
- **architecture.md §11 now says "All five providers do".** The sentence
|
||||
counts Checkers, and proxmox has one. The four Decisions bullets are
|
||||
entry 23's: the VMID from the UID with the ownership check, the mark in
|
||||
the description, the seed uploaded through the API, and the node
|
||||
resolved per call.
|
||||
- **The guide lists the ten design limits first, then four more.** They
|
||||
are the hibernated-VM latch, the one-request seed upload, the open-proxy
|
||||
sample (kvm2's guide lists it too), and "not yet proven end to end" with
|
||||
the three answers never captured. The Phase 6 to 8 guide items are
|
||||
each in the section where a reader looks for them. The config write
|
||||
replacing `agent` and `tags` is in *What lands on Proxmox*, and so are
|
||||
the seed cleanup, `qm unlock` and the 403 that makes Delete Permanent.
|
||||
The certificate rule is in *Cluster-side setup*. CA caching versus
|
||||
token rotation is in both.
|
||||
- **The guide resumes a hibernated VM with `qm start`, not
|
||||
`qm resume`.** A VM suspended to disk resumes from its state file on
|
||||
its next start; `qm resume` is for a VM paused in RAM.
|
||||
- **The guide's `pvesm add dir proxy-seeds --path /var/lib/proxy-seeds
|
||||
--content iso` is the standard form, not a recorded command.** Phase 0
|
||||
says only that the user created `proxy-seeds` as a `dir` storage with
|
||||
ISO content; the path is illustrative.
|
||||
|
||||
The token-file check, run without touching a cluster: `KUBECTL=echo`
|
||||
prints only the apply stage, and `KUBECTL=true` runs nothing. Scratch
|
||||
files covered one good line, two lines, a bare secret, blank lines around
|
||||
a good line, a non-PEM CA and a missing file. The real files on this
|
||||
workstation pass:
|
||||
|
||||
```bash
|
||||
make -n proxmox-token-secret
|
||||
make -s proxmox-token-secret PROXMOX_TOKEN_FILE=$D/<case> PROXMOX_CA_FILE=$D/ca KUBECTL=echo
|
||||
# good, ws: "apply -f -"; two, bad: "... is not one line of user@realm!tokenid=secret"; noca: "... holds no PEM certificate"
|
||||
make -s proxmox-token-secret KUBECTL=true && echo "real files pass" # real files pass
|
||||
```
|
||||
|
||||
The mutation check for the sample tests edited one line per run,
|
||||
asserting a single match. It ran the two tests, restored the file from a
|
||||
copy, and at the end `sha256sum -c` confirmed the three files. All five
|
||||
mutations failed the tests: `vlanTag: 5000` and `isoStorag:` in the
|
||||
sample, `192.168.2.8` and `type: kvm2` in the ConfigMap, and
|
||||
`image: debian-12` in the sample Proxy. `type: kvm2` was rerun after lint
|
||||
removed the test's own type comparison. It still fails, because
|
||||
`LoadConfig` refuses a proxmox block under another type.
|
||||
|
||||
The link sweep is a short script. It takes the docs as arguments, so it
|
||||
can be rerun after any later edit. It skips fenced code and inline code
|
||||
spans. For each relative link it checks that the file exists, and that
|
||||
the `#anchor` matches a GitHub-style slug of a heading in the target file
|
||||
(or the same file). Before the real run it was checked on a scratch copy
|
||||
of the guide with five links added. It reported exactly the three bad
|
||||
ones: a missing file, a missing anchor in `testing.md`, and a missing
|
||||
same-file anchor. It checked 78 relative links in the six edited docs,
|
||||
plus this log's, and found 0 broken:
|
||||
|
||||
```bash
|
||||
cat > doclinks.py <<'PY'
|
||||
import os, re, sys
|
||||
def slug(h, seen):
|
||||
s = re.sub(r"[^\w\- ]", "", h.strip().lower()).replace(" ", "-")
|
||||
n = seen.get(s, 0); seen[s] = n + 1
|
||||
return s if n == 0 else f"{s}-{n}"
|
||||
def prose(path):
|
||||
out, fence = [], False
|
||||
for line in open(path, encoding="utf-8"):
|
||||
if line.lstrip().startswith("```"): fence = not fence; continue
|
||||
if not fence: out.append(line)
|
||||
return out
|
||||
def anchors(path):
|
||||
seen = {}
|
||||
return {slug(re.sub(r"`", "", l.lstrip("#").strip()), seen) for l in prose(path) if re.match(r"#{1,6} ", l)}
|
||||
bad = 0
|
||||
for doc in sys.argv[1:]:
|
||||
for n, line in enumerate(prose(doc), 1):
|
||||
for target in re.findall(r"\]\(([^)\s]+)\)", re.sub(r"`[^`]*`", "", line)):
|
||||
if re.match(r"[a-z]+:", target): continue
|
||||
f, _, a = target.partition("#")
|
||||
p = os.path.normpath(os.path.join(os.path.dirname(doc), f)) if f else doc
|
||||
if not os.path.exists(p): print(f"{doc}: missing file {target}"); bad += 1
|
||||
elif a and p.endswith(".md") and a not in anchors(p): print(f"{doc}: missing anchor {target}"); bad += 1
|
||||
print(f"{bad} broken"); sys.exit(1 if bad else 0)
|
||||
PY
|
||||
python3 doclinks.py docs/proxmox-provider.md docs/providers-existing.md docs/architecture.md \
|
||||
README.md docs/providers.md CLAUDE.md docs/plans-executions/2026-09-18-1502-proxmox-provider.md # 0 broken
|
||||
```
|
||||
|
||||
Verification:
|
||||
|
||||
```bash
|
||||
bin/kustomize build config/default >/dev/null && bin/kustomize build config/samples >/dev/null # ok
|
||||
go test -count=1 -run 'TestLoadConfig_proxmoxSample' -v ./internal/provider/ # 3 PASS
|
||||
grep -c '—' docs/proxmox-provider.md # 0
|
||||
go build ./... && go vet ./... && go vet -tags=integration ./... && go test -short -count=1 ./... # all ok
|
||||
bin/golangci-lint run --disable staticcheck --max-same-issues 0 --max-issues-per-linter 0 ./internal/provider/
|
||||
git status --short config/ # the four edited files named above, plus proxy_proxmox.yaml
|
||||
```
|
||||
|
||||
Worth noting:
|
||||
|
||||
- Lint on `internal/provider` is at its baseline of three goconst hits.
|
||||
The first version of the new test pushed it to five: its
|
||||
`p.Type != "proxmox"` made `"proxmox"` a three-time literal, flagged in
|
||||
both `config.go` and the test. That comparison added nothing, so it
|
||||
went. `internal/provider/proxmox` was not touched.
|
||||
- The `tmp` volume comment in `manager.yaml` still says only the kvm
|
||||
provider stages seeds there. kvm2 and proxmox do too. It was already
|
||||
stale for kvm2, so it was left out of this phase's diff.
|
||||
- The architecture §5 block's right-hand bracket was aligned by
|
||||
character, not byte (`→` is three bytes), so every line puts `│` in
|
||||
column 83.
|
||||
- Nothing in this phase talked to the Proxmox host or a cluster.
|
||||
`.claude/settings.json` stays out of the commit. The parked kvm2 items
|
||||
(S5.1, and the 200 ms `TestCheck_hostsCheckedConcurrently`) did not
|
||||
bite: `go test -short ./...` passed.
|
||||
- Next is Phase 10: the first real run of `TestProxmox_lifecycle` and
|
||||
`TestProxmoxCheck_realHost` on pv1-1, with the env block in
|
||||
`docs/testing.md`. The three answers never captured go with it, and the
|
||||
guide's "Not yet proven end to end" gap and the README's matching
|
||||
clause come out once they pass. Then the CHANGELOG entry after the user
|
||||
confirms, and the MR.
|
||||
|
||||
Reference in New Issue
Block a user