Documentation
¶
Overview ¶
Package pveclient is a thin, hand-rolled client for the Proxmox VE JSON API. It deliberately avoids third-party PVE libraries so that auth, CSRF, task polling and error mapping are fully under the project's control.
Endpoint shape: https://<node>:<port>/api2/json/<path>. Most methods are node-scoped; the caller picks the target node per manifest. A few cluster endpoints accept the gatewayLabel placeholder and are routed to the configured gateway host.
Index ¶
- Constants
- Variables
- func IsInProgress(err error) bool
- func IsNotFound(err error) bool
- func IsTimeout(err error) bool
- func StatusPath(node, upid string) string
- func VolidFilename(volid, content string) string
- type APIError
- type Auth
- type Client
- func (c *Client) ClusterResources(ctx context.Context) ([]ClusterResource, error)
- func (c *Client) Do(ctx context.Context, method, node, path string, params url.Values, out any) (upID string, err error)
- func (c *Client) LXC() *LXC
- func (c *Client) LXCList(ctx context.Context, node string) ([]LXCListEntry, error)
- func (c *Client) LastReadAt() time.Time
- func (c *Client) LastWriteAt() time.Time
- func (c *Client) Nodes(ctx context.Context) ([]string, error)
- func (c *Client) PveVersion(ctx context.Context) (int, error)
- func (c *Client) Readiness(window time.Duration) bool
- func (c *Client) Storage() *Storage
- func (c *Client) StorageList(ctx context.Context, node string) ([]string, error)
- func (c *Client) URL(node, path string) string
- func (c *Client) VM() *VM
- func (c *Client) VMList(ctx context.Context, node string) ([]VMListEntry, error)
- func (c *Client) WritesPerformed() uint64
- type ClusterResource
- type ContentEntry
- type LXC
- func (l *LXC) CTTemplate(ctx context.Context, node string, cid int) (string, error)
- func (l *LXC) Clone(ctx context.Context, node string, src, dst int, params url.Values) (string, error)
- func (l *LXC) CloneTemplate(ctx context.Context, node string, src, dst int, params url.Values) (string, error)
- func (l *LXC) Create(ctx context.Context, node string, params url.Values, start bool) (string, error)
- func (l *LXC) Delete(ctx context.Context, node string, cid int) (string, error)
- func (l *LXC) Get(ctx context.Context, node string, cid int) (LXCConfig, error)
- func (l *LXC) IsTemplate(ctx context.Context, node string, cid int) (bool, error)
- func (l *LXC) MarkTemplate(ctx context.Context, node string, cid int) (string, error)
- func (l *LXC) Start(ctx context.Context, node string, cid int) (string, error)
- func (l *LXC) Status(ctx context.Context, node string, cid int) (LXCStatus, error)
- func (l *LXC) Stop(ctx context.Context, node string, cid int) (string, error)
- func (l *LXC) UnmarkTemplate(ctx context.Context, node string, cid int) (string, error)
- func (l *LXC) Update(ctx context.Context, node string, cid int, params url.Values) (string, error)
- type LXCConfig
- type LXCListEntry
- type LXCStatus
- type Options
- type PVEParams
- type Storage
- func (s *Storage) Content(ctx context.Context, node, storage string) ([]ContentEntry, error)
- func (s *Storage) Download(ctx context.Context, node, storage, downloadURL, filename, content string) (string, error)
- func (s *Storage) HasContent(ctx context.Context, node, storage, content, filename string) (bool, error)
- func (s *Storage) HasISO(ctx context.Context, node, storage, filename string) (bool, error)
- type TaskFailedError
- type TaskID
- type TaskNotFound
- type TaskStatus
- type TaskTimeoutError
- type TaskWaiter
- type UPID
- type VM
- func (vm *VM) Clone(ctx context.Context, node string, srcID, newID int, full bool, name string) (string, error)
- func (vm *VM) Create(ctx context.Context, node string, params url.Values, start bool) (string, error)
- func (vm *VM) Delete(ctx context.Context, node string, vmid int) (string, error)
- func (vm *VM) Get(ctx context.Context, node string, vmid int) (VMConfig, error)
- func (vm *VM) IsTemplate(ctx context.Context, node string, vmid int) (bool, error)
- func (vm *VM) MarkTemplate(ctx context.Context, node string, vmid int) (string, error)
- func (vm *VM) Reboot(ctx context.Context, node string, vmid int) (string, error)
- func (vm *VM) Resize(ctx context.Context, node string, vmid int, memoryMib int64) (string, error)
- func (vm *VM) Shutdown(ctx context.Context, node string, vmid int) (string, error)
- func (vm *VM) Start(ctx context.Context, node string, vmid int) (string, error)
- func (vm *VM) Status(ctx context.Context, node string, vmid int) (VMStatus, error)
- func (vm *VM) Stop(ctx context.Context, node string, vmid int) (string, error)
- func (vm *VM) Update(ctx context.Context, node string, vmid int, params url.Values) (string, error)
- type VMConfig
- type VMListEntry
- type VMStatus
Constants ¶
const TaskPollInterval = 5 * time.Second
TaskPollInterval is the default cadence for polling a running PVE task.
Variables ¶
var ErrNoBaseURL = errors.New("pveclient: no base-url configured")
ErrNoBaseURL is returned by New when neither Options.GlobalBaseURL nor PVEParams.BaseURL is set. Configuration should reject this via config.Validate before the client is constructed; the test paths set GlobalBaseURL directly.
var ErrReadOnly = errors.New("pve write circuit breaker open: running read-only")
ErrReadOnly is returned by a write call while the write circuit breaker is open. The client keeps reading PVE state; only mutations are gated.
Functions ¶
func IsInProgress ¶
IsInProgress reports whether err is PVE's "operation already in progress". The executor uses this to re-attach to the existing task instead of failing (statelessness: after a restart an in-flight op may still be running on PVE).
func IsNotFound ¶
IsNotFound reports whether err indicates a missing PVE resource. It matches both real HTTP 404s and PVE's 500-with-"no such ..." convention.
func StatusPath ¶
StatusPath builds the task-status API path for a TaskID.
func VolidFilename ¶
VolidFilename extracts the filename from a PVE content volid. Forms: "<storage>:iso/<filename>", "<storage>:vztmpl/<filename>", "<storage>:backup/<path>", etc. Exported so adopt can reuse it when reverse-engineering storage listings into proxops artifact manifests.
Types ¶
type APIError ¶
APIError is a semantic PVE API failure: an HTTP error status with a decodable PVE error envelope, or a non-recoverable authentication failure.
PVE does not use 404 semantics consistently — a missing VM typically returns HTTP 500 with the message "no such vm" (PVE-VERIFY on a live host).
type Auth ¶
type Auth struct {
// contains filtered or unexported fields
}
Auth implements PVE API authentication. Two methods are supported:
- token (default) — PVE API token, sent on every request as Authorization: PVEAPIToken=<user>@<realm>!<id>=<uuid>. No CSRF handling, no expiry. This is the recommended path.
- ticket — user/password exchanged for a session ticket via POST /access/ticket. Subsequent requests carry PVEAuthCookie/X-Userid cookies and an X-CSRF-Token header. Tickets expire (PVE auth timeout); the client proactively refreshes and re-exchanges on 401.
NOTE (M1 live-host verification): exact cookie names and whether PVEAPIToken requests still need X-CSRF-Token should be confirmed on a real PVE. The token path deliberately sends no CSRF header.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a PVE API client. Safe for concurrent use.
All traffic is routed through one base URL (c.baseURL). The PVE node name appears in the request path (/nodes/<n>/...) and in the UPID prefix, but NEVER in the host: PVE's cluster-wide endpoints (/cluster/resources, /access/ticket, /version) exist on every node, so one API endpoint is enough to reach every object on every node.
func New ¶
New builds a Client. CAFile may be empty (the system trust store is used).
The effective base URL is Options.BaseURL when set (test override), else PVEParams.BaseURL. New returns ErrNoBaseURL if neither is set — that is a configuration error config.Validate catches earlier in production.
func (*Client) ClusterResources ¶
func (c *Client) ClusterResources(ctx context.Context) ([]ClusterResource, error)
ClusterResources returns the full cluster resource list via the gateway, decoded into a narrow typed struct. Each entry carries the union of PVE's listing fields: one entry per VM, per LXC, and per other resource kind on the cluster.
func (*Client) Do ¶
func (c *Client) Do(ctx context.Context, method, node, path string, params url.Values, out any) (upID string, err error)
Do performs a PVE API request with retry/backoff. It applies authentication, maps PVE error envelopes, and (for POST/DELETE) counts toward the write circuit breaker.
- GET: params go in the query string.
- POST/DELETE: params are form-encoded in the body.
- out: when non-nil, the response "data" object is decoded into it.
If the response "data" is a JSON string, it is a task UPID and is returned as upID (out is left untouched in that case).
func (*Client) LXCList ¶
LXCList lists the Linux containers on one node. GET /nodes/{n}/lxc (read-only). PVE returns one entry per CT with `vmid` + `name`, `status`, `tags`.
func (*Client) LastReadAt ¶
LastReadAt returns the time of the last successful PVE read.
func (*Client) LastWriteAt ¶
LastWriteAt returns the time of the last successful PVE write.
func (*Client) PveVersion ¶
PveVersion returns the PVE major version as reported by /version. The `gatewayLabel` node is a marker that "this is a cluster-wide call"; routing still goes through c.baseURL.
func (*Client) Readiness ¶
Readiness reports whether the client has successfully read PVE recently (within the given window) — used by /healthz.
func (*Client) StorageList ¶
StorageList lists the storage backends configured on one node. GET /nodes/{n}/storage (read-only). Each entry's `storage` key is the PVE storage id used in volume ids (e.g. "local", "local-lvm"). PVE also emits an `id` alias on some builds; the `storage` key is the canonical one.
func (*Client) URL ¶
URL returns the full API URL for the request. The `node` argument is part of the path only — it has NO effect on host selection. This is the key property of the single-endpoint PVE model: a node name that is not a DNS hostname can still be reached, because the host is fixed from config.
func (*Client) VMList ¶
VMList lists the qemu VMs on one node. GET /nodes/{n}/qemu (read-only). PVE returns one entry per VM with vmid, name, status and tags.
func (*Client) WritesPerformed ¶
WritesPerformed returns the number of PVE write requests the client has issued since construction (one count per POST/PUT/DELETE attempt, not per retry). A purely read-only caller — `adopt` — must observe 0, and tests pin that.
type ClusterResource ¶
type ClusterResource struct {
Node string `json:"node"`
Vmid int `json:"vmid"`
Type string `json:"type"`
Kind string `json:"kind"`
Status string `json:"status"`
Name string `json:"name"`
// Tags: PVE actually returns a space-joined string; mock returns string.
Tags string `json:"tags"`
Pool string `json:"pool"`
}
ClusterResource is one entry of PVE's /cluster/resources listing.
PVE's real listing uses "type" ("vm" → qm/qemu, "ct" → lxc); the mock also emits "kind" as an alias. We capture both so the planner can normalize.
type ContentEntry ¶
type ContentEntry struct {
Volid string `json:"volid"` // "local:iso/name.iso"
Content string `json:"content"` // "iso" | "vztmpl" | "backup" | ...
Format string `json:"format"` // "iso" | "tzst" | "dir" | ...
Size int64 `json:"size"`
}
ContentEntry is one entry of GET /nodes/{n}/storage/{s}/content. PVE's "bare" content listing reports every volume across all content types supported by the storage — each entry has `content` set to one of "iso", "vztmpl", "backup", "rootdir", "images", ... depending on the pool. This is required on PVE 9.2 because `GET /storage/{s}/content/iso` and `/content/vztmpl` 500 "unable to parse directory volume name" even when the listing contains entries of that type — the type segment is (mis)parsed as a volid.
type LXC ¶
type LXC struct {
// contains filtered or unexported fields
}
LXC bundles the /nodes/{n}/lxc/{cid} endpoints. Mutations return the task UPID where PVE is asynchronous.
func (*LXC) CTTemplate ¶
CTTemplate promotes a stopped, non-template container to a template when it is not one yet. Idempotent: a container that is already a template returns an empty upid string and nil error. PVE's /lxc/{cid}/template is only valid on stopped containers — callers must stop first when necessary.
func (*LXC) Clone ¶
func (l *LXC) Clone(ctx context.Context, node string, src, dst int, params url.Values) (string, error)
Clone clones a template into a new container id. PVE requires the source to be stopped or a template. Returns the PVE task UPID.
func (*LXC) CloneTemplate ¶
func (l *LXC) CloneTemplate(ctx context.Context, node string, src, dst int, params url.Values) (string, error)
CloneTemplate creates a new template from a source (usually a CTTemplate). This is the M4 CTTemplate reconcile primitive: desired is "a template exists with cid X", and we get it by cloning from a source id + marking the result as a template.
func (*LXC) Create ¶
func (l *LXC) Create(ctx context.Context, node string, params url.Values, start bool) (string, error)
Create submits a ctcreate. start=true boots the container after creation.
func (*LXC) IsTemplate ¶
IsTemplate reports whether a container is flagged as a PVE template (config has "template" == "1").
func (*LXC) MarkTemplate ¶
MarkTemplate promotes a stopped container to a template (POST /lxc/{cid}/template).
func (*LXC) UnmarkTemplate ¶
UnmarkTemplate demotes a template back to a regular container (POST /lxc/{cid}/untemplate).
func (*LXC) Update ¶
Update performs a config change on a container.
PVE verb: **PUT**, not POST. Probed on the conformance-dev PVE 9.2.2 cluster 2026-09-08: `POST /nodes/{n}/lxc/{id}/config` returns `501 Method ... not implemented` while `PUT .../config` returns 200 (task UPID or null when nothing changed). LXC *power* ops (start/stop/reboot/shutdown) remain POST; only config-set moved to PUT in PVE 9.x.
type LXCListEntry ¶
type LXCListEntry struct {
CID int `json:"vmid"`
Name string `json:"name"`
Status string `json:"status"`
Tags string `json:"tags"`
}
LXCListEntry is one row of GET /nodes/{n}/lxc.
PVE's LXC listing uses the shared PVE object-id namespace: the key is `vmid` (even though some LXC config endpoints use `cid` elsewhere). The mock PVE mirrors this so proxops can consume one shape.
type LXCStatus ¶
type LXCStatus struct {
CID int `json:"cid"`
Status string `json:"status"` // running | stopped
Maxmem int64 `json:"maxmem"`
MemUtil *float64 `json:"mem"`
DiskUtil *float64 `json:"disk"`
Uptime *float64 `json:"uptime"`
}
LXCStatus mirrors /nodes/{n}/lxc/{cid}/status-current.
type Options ¶
type Options struct {
PVE PVEParams
// HTTPTimeout bounds a single API request (task waits are separate); default 30s.
HTTPTimeout time.Duration
// BreakerThreshold is consecutive write failures before read-only mode; default 20.
BreakerThreshold int
// BreakerDuration is how long the write circuit stays open; default 10m.
BreakerDuration time.Duration
// BaseURL, when set, overrides PVE.BaseURL and routes EVERY request
// (cluster-wide, per-node, ticket exchange) here. Used by tests that
// point the client at the stateful mock PVE. Production leaves it empty
// and relies on PVE.BaseURL.
BaseURL string
}
Options configure a Client.
type PVEParams ¶
type PVEParams struct {
User string // token owner or ticket user, e.g. "root@pam"
Auth string // "token" (default) or "ticket"
TokenID string
TokenValue string // composed user@realm!id=uuid
Token string
Password string
// BaseURL is "scheme://host[:port]". Required in production.
BaseURL string
// Nodes is an optional allowlist of PVE node names. Parsed/validated
// at the config layer; the client keeps it for informational purposes
// (a future "node not in allowlist" pre-check can use it).
Nodes []string
CAFile string
}
PVEParams carries PVE connection settings into New (decoupled from the config package).
PVE exposes the entire JSON API on every node; the agent talks to ONE endpoint (BaseURL) for all traffic. Node identity lives only in the request path, not the host.
func (PVEParams) Credential ¶
Credential composes the PVEAPI token credential from the user, token id, and raw token value, OR returns the pre-composed TokenValue when present. Empty user / token id / token → empty string (caller MUST check for empty and refuse the request, since PVE rejects unauthenticated calls).
For ticket auth, Credential is unused (the ticket flow is user+password, see auth.fetchTicket); this method only models the token wire shape.
type Storage ¶
type Storage struct {
// contains filtered or unexported fields
}
Storage bundles the /nodes/{n}/storage endpoints used for ISO and CTTemplate (vztmpl) artifact management.
func (*Storage) Content ¶
Content lists all volume entries on a node storage (across every content type the storage supports). Callers that want only ISOs can filter on entry.Content == "iso"; callers that want only LXC templates filter on entry.Content == "vztmpl".
func (*Storage) Download ¶
func (s *Storage) Download(ctx context.Context, node, storage, downloadURL, filename, content string) (string, error)
Download issues an ISO, CTTemplate or DiskImage download to the storage backend. The `content` parameter tells PVE which pool the file belongs to:
- "iso": PVE downloads to `<storage>:iso/<filename>`
- "vztmpl": PVE downloads to `<storage>:vztmpl/<filename>`
Endpoint contract (PVE 9.2.x — probed live on conformance-dev, PVE 9.2.2 release):
POST /nodes/{n}/storage/{s}/download-url
form: url=<https URI>, filename=<name>, content=<iso|vztmpl>
returns a task UPID. `POST .../download` was the PVE 8-era path and on PVE 9.2 dir storage returns 501 "Method 'POST /nodes/.../storage/.../download' not implemented" — the endpoint was renamed. Probes also confirmed `GET` on `/download-url` is 501 (POST-only) and that a URL the node cannot fetch still returns 200 + task UPID, with exitstatus `"download failed: exit code 8"` on the task (curl exit 8 = "Could not connect to server"). proxops therefore always treats a 5xx or task exitstatus-other-than-OK as a real failure and relies on Storage.HasContent for the eventual convergence test: the file appears on the storage pool after the task succeeds.
func (*Storage) HasContent ¶
func (s *Storage) HasContent(ctx context.Context, node, storage, content, filename string) (bool, error)
HasContent reports whether a file with the given name + PVE content type (e.g. "iso" or "vztmpl") exists on the storage backend.
type TaskFailedError ¶
TaskFailedError is returned by WaitTask when a PVE task terminates without an OK exit status.
func (*TaskFailedError) Error ¶
func (e *TaskFailedError) Error() string
type TaskNotFound ¶
type TaskNotFound struct {
TaskID
}
TaskNotFound is returned when a task UPID cannot be found on PVE.
func (*TaskNotFound) Error ¶
func (e *TaskNotFound) Error() string
type TaskStatus ¶
type TaskStatus struct {
Status string `json:"status"`
Progress *float64 `json:"progress"`
ExitStatus string `json:"exitstatus"`
}
TaskStatus is the subset of PVE's task-status response we consume.
GET /nodes/{n}/tasks/{upid}/status returns, when a task settles:
{"data": {"status": "stopped", "exitstatus": "OK"}}
{"data": {"status": "stopped", "exitstatus": "unable to create VM 9100 -
lvcreate 'pve/vm-9100-disk-0' error: Volume too large (8.00 EiB)"}}
i.e. PVE reports a failed task's reason in the exitstatus STRING (verified against PVE 9.2's task-status endpoint; success is the literal "OK"). The "errors" field belongs to the *parameter-validation* envelope of HTTP 4xx responses and is never populated on task status, so it is not decoded here — decoding it previously surfaced every failed task as "unknown error".
While running: {"status": "running", "progress": 0.42, ...}.
func (TaskStatus) IsOK ¶
func (s TaskStatus) IsOK() bool
IsOK reports whether the task terminated successfully.
PVE task exit-status vocabulary:
- "OK" → success
- "WARNINGS: N" → success with N cosmetic warnings (PVE's exit code "0 with warnings"; still a success)
- "ERROR: <text>" / "FAILED: <text>" → failure
Treating "WARNINGS:" as success matches PVE's own semantics (e.g. `qm` returns exit code 0 when it emits a warning).
func (TaskStatus) IsStopped ¶
func (s TaskStatus) IsStopped() bool
IsStopped reports whether the task has terminated.
type TaskTimeoutError ¶
TaskTimeoutError is returned by WaitTask when a task had not completed by the deadline. The task may still complete on PVE's side; the next reconcile cycle re-diffs live state, which is authoritative for reporting.
func (*TaskTimeoutError) Error ¶
func (e *TaskTimeoutError) Error() string
type TaskWaiter ¶
type TaskWaiter struct {
// contains filtered or unexported fields
}
TaskWaiter polls PVE task status until completion.
The overall deadline is carried on ctx — the reconcile executor wraps Wait with context.WithTimeout(Rec.TaskTimeout).
func NewTaskWaiter ¶
func NewTaskWaiter(c *Client, log *slog.Logger) *TaskWaiter
NewTaskWaiter builds a TaskWaiter with the default 5s poll interval.
func (*TaskWaiter) SetInterval ¶
func (w *TaskWaiter) SetInterval(d time.Duration)
SetInterval overrides the polling interval.
func (*TaskWaiter) Status ¶
func (w *TaskWaiter) Status(ctx context.Context, tid TaskID) (TaskStatus, error)
Status fetches the task's current state.
func (*TaskWaiter) Wait ¶
func (w *TaskWaiter) Wait(ctx context.Context, tid TaskID) (*TaskStatus, error)
Wait polls the task until it stops and returns the final status.
Results:
- (*TaskStatus, nil) → task completed with exitstatus OK.
- (*TaskFailedError, err) → task finished, exitstatus != OK.
- (*TaskTimeoutError, err) → ctx deadline exceeded; last observed status is attached to the error via LastStatus.
- (*TaskNotFound, err) → task vanished from PVE (reaped / foreign).
Transient read errors during polling are logged and retried.
type UPID ¶
type UPID struct {
Raw string
Node string
PID int
// After is the part after "node@pid!" (the task part).
After string
}
UPID is a PVE task identifier. Real PVE UPIDs look like
pve01@25012!VM:106:1640528320:root@pam
i.e. "<node>@<pid>!<task-part>". The exact task-part grammar is not a stable contract, so only the node is parsed out with meaning; Raw stays authoritative for URL construction.
type VM ¶
type VM struct {
// contains filtered or unexported fields
}
VM bundles the /nodes/{n}/qemu/{vmid} endpoints.
Mutating operations return the PVE task UPID ("" when PVE performs the change synchronously) so callers can drive a TaskWaiter when they need to block on completion.
func (*VM) Clone ¶
func (vm *VM) Clone(ctx context.Context, node string, srcID, newID int, full bool, name string) (string, error)
Clone performs a PVE clone (POST /nodes/{n}/qemu/{src}/clone). M12.
Wire facts (probe-verified on conformance-dev PVE 9.2.2):
- `newid` is required; `full=1` requests a full clone (independent volumes); `name` sets the destination display name (PVE does NOT copy the source's name otherwise).
- `start` is REJECTED ("property is not defined in schema") — power is a separate status/start call.
- A missing source fails SYNCHRONOUSLY with HTTP 500 "unable to find configuration file for VM <src> on node '<n>'" (no UPID).
- An existing destination id fails synchronously with HTTP 500 "unable to create VM <newid>: config file already exists".
- The clone copies the source's whole /config (identity fields included); the caller is responsible for the post-clone config write — see exec.cloneCreate.
Returns the task UPID on success.
func (*VM) Create ¶
func (vm *VM) Create(ctx context.Context, node string, params url.Values, start bool) (string, error)
Create submits a vmcreate. `params` carries PVE snake_case keys (name, memory, cpu, cores, net0, ...). start=true also boots the VM.
func (*VM) Delete ¶
Delete removes a stopped VM. PVE 9.x exposes VM destruction as REST DELETE /nodes/{n}/qemu/{v} — the legacy POST /qemu/{v}/vmdelete endpoint is "not implemented" in PVE 9.2. The VM must already be stopped; the executor performs a pre-delete stop (PVE refuses to destroy a running object; we never send "force").
func (*VM) IsTemplate ¶
IsTemplate reports whether PVE reports `template=1` on the object's live /config. Used by the planner to detect the VM↔TemplateVM kind-mismatch shape: a proxops VM desired against a PVE-side template. PVE 9.2 has no /qemu/{id}/untemplate endpoint (probe-verified: HTTP 501 "not implemented"), so proxops surfaces this mismatch as a non-destructive anomaly instead of attempting a write.
func (*VM) MarkTemplate ¶
MarkTemplate promotes a stopped qemu VM to a PVE template. PVE requires the object to be stopped; the planner/executor pre-stop when needed. Asynchronous: returns a task UPID.
func (*VM) Resize ¶
Resize adjusts online RAM in MiB (POST /qemu/{v}/resize) — the same unit as PVE's static `memory` config field. Synchronous on PVE; returns an empty upID.
type VMConfig ¶
VMConfig mirrors the PVE VM (qemu) config response. Keys are heterogeneous, so we keep a generic map and add typed helpers where the reconciler needs them (in M2/M3).
type VMListEntry ¶
type VMListEntry struct {
VMID int `json:"vmid"`
Name string `json:"name"`
Status string `json:"status"`
Tags string `json:"tags"`
}
VMListEntry is one row of GET /nodes/{n}/qemu.
type VMStatus ¶
type VMStatus struct {
VMID int `json:"vmid"`
Status string `json:"status"` // running | stopped | paused
Maxmem int64 `json:"maxmem"`
CPUs int `json:"cpus"`
CPUUtil *float64 `json:"cpu"`
MemUtil *float64 `json:"mem"`
DiskUtil *float64 `json:"disk"`
NetRx *float64 `json:"netin"`
NetTx *float64 `json:"netout"`
Uptime *float64 `json:"uptime"`
}
VMStatus mirrors /nodes/{n}/qemu/{vmid}/status-current. Memory fields are in bytes.