proxmox api
Corral serves a useful subset of the Proxmox VE REST
API (/api2/json/β¦),
translated live onto KubeVirt. Proxmox ecosystem tools β the Terraform
bpg/proxmox provider, Ansible community.general.proxmox_* modules,
proxmoxer, monitoring scripts β can list, create, start/stop, and delete
KubeVirt VMs without knowing corral exists.
Implementation: pkg/proxmox (verified against bpg/proxmox v0.109.0).
ADR: adr/0001-k8s-rbac-to-proxmox-privileges.md.
Two ways to run itβ
- Mounted in
corral webβ the dashboard server mounts the handler at/api2/json/automatically (pkg/web/server.go), so a singlecorral web(or the on-cluster deployment) speaks both the corral REST API and the Proxmox API on the same port (8006 β Proxmox's own port, deliberately). - Standalone plugin β
corral plugin install proxmox, thencorral proxmox serve [--addr :8006] [--cert β¦ --key β¦]. Thebpg/proxmoxTerraform provider requires TLS, hence the cert flags.
Architectureβ
Three modules, all behind the shell.Runner seam (hermetically testable
with shell.Fake β see server_test.go and server_compat_test.go, which
pin response shapes against what bpg/proxmox expects):
| Module | Role |
|---|---|
server.go | HTTP routing, auth, the Proxmox {"data": β¦} envelope |
queries.go | kubectl JSON adapters: NodeQuery, StorageQuery, RBACQuery, VMIDLabelQuery |
translate.go | Pure mapping functions: vmid hashing, status vocab, memory parsing, UPIDs, role tables |
VMID mappingβ
Proxmox addresses VMs by numeric vmid; KubeVirt by name. Two-way resolution:
- VMs created through this API get a
corral.io/proxmox-vmid=<vmid>label, so the requested vmid round-trips exactly. - Pre-existing VMs get a deterministic fallback id:
100 + crc32(name) % 899999900(VmidFor). No state to store; collisions are theoretically possible but irrelevant at homelab scale.
Tasks (UPIDs)β
Proxmox operations are async and return a UPID; corral's operations are
synchronous. Mutating endpoints return a fabricated UPID and
GET /nodes/{node}/tasks/{upid}/status always reports
stopped/exitstatus: OK. Tools that poll task status complete instantly.
Authβ
authorized() accepts both styles Proxmox tooling speaks, against a single
shared secret:
- API token header
Authorization: PVEAPIToken=user@realm!id=SECRET(Terraform's stateless path) - Ticket cookie
PVEAuthCookie=PVE:user:SECRET, issued byPOST /access/ticketwhen the login password equals the secret
Set the secret with corral proxmox serve --token <secret> or the
CORRAL_PROXMOX_TOKEN env var (also respected by the handler mounted in
corral web). With no secret configured the API is open β acceptable
only because corral deployments are gated by tailnet membership. Never
expose it off the tailnet.
Endpoint inventoryβ
| Endpoint | Behavior |
|---|---|
POST /access/ticket | Issues a ticket; checks password against the shared secret if set |
GET /version | Static 8.2.0 (repoid: corral-kubevirt) |
GET /nodes | K8s nodes β Proxmox nodes (Ready β online) |
GET /nodes/{n}/status | Per-node CPU/memory capacity |
GET /nodes/{n}/storage | StorageClasses β Proxmox storage (longhorn β lvmthin, else dir) |
GET /nodes/{n}/time, /dns, /hosts | Plausible static answers |
GET /cluster/resources | VMs (type=qemu) + nodes, filterable with ?type= |
GET /nodes/{n}/qemu | VM list for a node (unplaced/stopped VMs show on every node) |
POST /nodes/{n}/qemu | Create: maps vmid, name, cores, memory (MB); labels the VM with its vmid |
GET β¦/qemu/{vmid}/status/current | Status, cpus, maxmem, guest-agent flag |
POST β¦/qemu/{vmid}/status/{action} | start / stop / shutdown / reset / reboot (stop β graceful KubeVirt stop) |
GET β¦/qemu/{vmid}/config | name, cores, memory (MB), ostype l26, virtio net stub |
DELETE β¦/qemu/{vmid} | Full corral delete (VM + disks + snapshots + proxy resources) |
POST β¦/qemu/{vmid}/vncproxy, /termproxy | Stub tickets pointing at corral's websocket bridges (see gaps) |
GET /access/users, /groups, /roles | K8s ClusterRoleBindings β Proxmox users/groups; fixed role table (ADR-0001) |
GET /pools, /cluster/ha/groups/, /nodes/{n}/lxc | Valid empty answers (no pools/HA-groups/LXC) |
anything else under /api2/json/ | Proxmox-shaped 404 + [proxmox-gap] stderr log for gap discovery |
The catch-all gap log is the discovery mechanism: run a new tool against
corral, grep the server log for [proxmox-gap], and you have the exact list
of endpoints it needs.
Known gaps / room for improvementβ
Reviewed 2026-06-12. Fixed in this pass:
β deleted; the plugin now only wrapscmd/corral-proxmoxduplicated all ofpkg/proxmox(~1,000 lines, case-renamed)proxmox.NewHandler.β now wired toServer.tokenexisted but nothing could set it--token/CORRAL_PROXMOX_TOKEN.
Remaining, roughly by value:
- Create ignores disks.
POST /nodes/{n}/qemumaps only vmid/name/cores/memory βscsi0/virtio0/ide2(disk and CD-ROM specs) are dropped, so an API-created VM has no boot source. Mappingstorage:sizeto a blank PVC andisomedia to the CDI ISO path would make Terraform-provisioned VMs actually bootable. PUT/POST β¦/confignot implemented. Terraform updates (cpu/memory resize) 404. Corral already hasScale()β wiring it in is cheap.- vncproxy/termproxy tickets aren't connectable by stock Proxmox
clients: they expect a TCP VNC endpoint plus the
vncwebsocketendpoint, while corral exposes its own websocket bridges (/api/vnc/{ns}/{name}). ImplementingGET β¦/qemu/{vmid}/vncwebsocketas a passthrough to the existing bridge would let noVNC-based tools connect for real. - Metrics are zeros.
uptime,cpu,mem,maxdiskare 0 in node and VM rows; dashboards render but look idle. Could be populated from metrics-server when present (corral web already has that plumbing). - Snapshot endpoints missing. Corral supports VM snapshots; Proxmox
tools use
GET/POST β¦/qemu/{vmid}/snapshot. Another cheap win. findVMlists all VMs up to twice per request (label lookup, then crc32 fallback). Fine at homelab scale; an index/cache would help at hundreds of VMs.- vmid collisions are unhandled for unlabeled VMs (crc32 truncation). Worst case two VMs answer to the same vmid; first match wins.