pveclient

package
v0.6.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

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

View Source
const TaskPollInterval = 5 * time.Second

TaskPollInterval is the default cadence for polling a running PVE task.

Variables

View Source
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.

View Source
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

func IsInProgress(err error) bool

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

func IsNotFound(err error) bool

IsNotFound reports whether err indicates a missing PVE resource. It matches both real HTTP 404s and PVE's 500-with-"no such ..." convention.

func IsTimeout

func IsTimeout(err error) bool

IsTimeout reports whether err is a TaskTimeoutError.

func StatusPath

func StatusPath(node, upid string) string

StatusPath builds the task-status API path for a TaskID.

func VolidFilename

func VolidFilename(volid, content string) string

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

type APIError struct {
	StatusCode int
	PVECode    int
	Message    string
	Target     string
}

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).

func (*APIError) Error

func (e *APIError) Error() string

type Auth

type Auth struct {
	// contains filtered or unexported fields
}

Auth implements PVE API authentication. Two methods are supported:

  1. 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.
  2. 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.

func (*Auth) Method

func (a *Auth) Method() string

Method returns the active auth method.

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

func New(o Options, log *slog.Logger) (*Client, error)

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) LXC

func (c *Client) LXC() *LXC

LXC returns an LXC handle.

func (*Client) LXCList

func (c *Client) LXCList(ctx context.Context, node string) ([]LXCListEntry, error)

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

func (c *Client) LastReadAt() time.Time

LastReadAt returns the time of the last successful PVE read.

func (*Client) LastWriteAt

func (c *Client) LastWriteAt() time.Time

LastWriteAt returns the time of the last successful PVE write.

func (*Client) Nodes

func (c *Client) Nodes(ctx context.Context) ([]string, error)

Nodes lists the PVE cluster's node names. GET /cluster/nodes (read-only).

func (*Client) PveVersion

func (c *Client) PveVersion(ctx context.Context) (int, error)

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

func (c *Client) Readiness(window time.Duration) bool

Readiness reports whether the client has successfully read PVE recently (within the given window) — used by /healthz.

func (*Client) Storage

func (c *Client) Storage() *Storage

Storage returns a Storage handle.

func (*Client) StorageList

func (c *Client) StorageList(ctx context.Context, node string) ([]string, error)

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

func (c *Client) URL(node, path string) string

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) VM

func (c *Client) VM() *VM

VM returns a VM handle.

func (*Client) VMList

func (c *Client) VMList(ctx context.Context, node string) ([]VMListEntry, error)

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

func (c *Client) WritesPerformed() uint64

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

func (l *LXC) CTTemplate(ctx context.Context, node string, cid int) (string, error)

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) Delete

func (l *LXC) Delete(ctx context.Context, node string, cid int) (string, error)

Delete removes a container, stopping it first if it is running.

func (*LXC) Get

func (l *LXC) Get(ctx context.Context, node string, cid int) (LXCConfig, error)

Get retrieves a container's full config.

func (*LXC) IsTemplate

func (l *LXC) IsTemplate(ctx context.Context, node string, cid int) (bool, error)

IsTemplate reports whether a container is flagged as a PVE template (config has "template" == "1").

func (*LXC) MarkTemplate

func (l *LXC) MarkTemplate(ctx context.Context, node string, cid int) (string, error)

MarkTemplate promotes a stopped container to a template (POST /lxc/{cid}/template).

func (*LXC) Start

func (l *LXC) Start(ctx context.Context, node string, cid int) (string, error)

Start boots a stopped container.

func (*LXC) Status

func (l *LXC) Status(ctx context.Context, node string, cid int) (LXCStatus, error)

Status returns the live container status.

func (*LXC) Stop

func (l *LXC) Stop(ctx context.Context, node string, cid int) (string, error)

Stop halts a running container.

func (*LXC) UnmarkTemplate

func (l *LXC) UnmarkTemplate(ctx context.Context, node string, cid int) (string, error)

UnmarkTemplate demotes a template back to a regular container (POST /lxc/{cid}/untemplate).

func (*LXC) Update

func (l *LXC) Update(ctx context.Context, node string, cid int, params url.Values) (string, error)

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 LXCConfig

type LXCConfig map[string]any

LXCConfig mirrors the PVE LXC container config response.

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.

func (LXCStatus) IsStopped

func (s LXCStatus) IsStopped() bool

IsStopped reports whether the container is not running.

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

func (p PVEParams) Credential() string

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

func (s *Storage) Content(ctx context.Context, node, storage string) ([]ContentEntry, error)

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.

func (*Storage) HasISO

func (s *Storage) HasISO(ctx context.Context, node, storage, filename string) (bool, error)

HasISO is a backward-compat convenience that uses the bare content listing (filter by content="iso"). On PVE 9.2 dir storage this is the only way to read the ISO pool (see ContentEntry doc).

type TaskFailedError

type TaskFailedError struct {
	UPID    string
	Message string
}

TaskFailedError is returned by WaitTask when a PVE task terminates without an OK exit status.

func (*TaskFailedError) Error

func (e *TaskFailedError) Error() string

type TaskID

type TaskID struct {
	Node string
	UPID string
}

TaskID identifies a PVE task.

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

type TaskTimeoutError struct {
	UPID       string
	Elapsed    time.Duration
	LastStatus string
}

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.

func ParseUPID

func ParseUPID(s string) UPID

ParseUPID parses a PVE task identifier. It never fails; unrecognized input degrades to a Raw-only UPID.

func (UPID) String

func (u UPID) String() string

String returns the raw UPID.

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

func (vm *VM) Delete(ctx context.Context, node string, vmid int) (string, error)

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) Get

func (vm *VM) Get(ctx context.Context, node string, vmid int) (VMConfig, error)

Get retrieves a VM's full config. Not found → *APIError with IsNotFound.

func (*VM) IsTemplate

func (vm *VM) IsTemplate(ctx context.Context, node string, vmid int) (bool, error)

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

func (vm *VM) MarkTemplate(ctx context.Context, node string, vmid int) (string, error)

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) Reboot

func (vm *VM) Reboot(ctx context.Context, node string, vmid int) (string, error)

Reboot restarts the guest OS.

func (*VM) Resize

func (vm *VM) Resize(ctx context.Context, node string, vmid int, memoryMib int64) (string, error)

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.

func (*VM) Shutdown

func (vm *VM) Shutdown(ctx context.Context, node string, vmid int) (string, error)

Shutdown requests a graceful ACPI shutdown.

func (*VM) Start

func (vm *VM) Start(ctx context.Context, node string, vmid int) (string, error)

Start boots a stopped VM.

func (*VM) Status

func (vm *VM) Status(ctx context.Context, node string, vmid int) (VMStatus, error)

Status returns the live VM status.

func (*VM) Stop

func (vm *VM) Stop(ctx context.Context, node string, vmid int) (string, error)

Stop issues a hard stop.

func (*VM) Update

func (vm *VM) Update(ctx context.Context, node string, vmid int, params url.Values) (string, error)

Update performs a config change (POST /qemu/{v}/config). Must exclude vmid.

type VMConfig

type VMConfig map[string]any

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.

func (VMStatus) IsStopped

func (s VMStatus) IsStopped() bool

IsStopped reports whether the VM is not running.

Directories

Path Synopsis
Package mock implements a stateful in-memory PVE API server for tests.
Package mock implements a stateful in-memory PVE API server for tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL