feat(models): implement typed response models for PVE 8.x and 9.x

Add C# model classes for all PVE API resources: nodes, VMs, VM config,
containers, storage, network interfaces, SDN zones/vnets, users, roles,
permissions, snapshots, tasks, and cluster status. All models use dual
JSON attributes (System.Text.Json + Newtonsoft.Json), nullable optional
fields, and human-readable ToString() overrides.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clint Branham
2026-03-17 15:45:06 -05:00
parent 2c7e90d185
commit 4ac38f7714
20 changed files with 2085 additions and 0 deletions
@@ -0,0 +1,54 @@
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Vms
{
/// <summary>
/// Represents the Cloud-Init specific configuration fields for a VM.
/// Fields are extracted from the full VM config.
/// </summary>
public class PveCloudInitConfig
{
/// <summary>Default user name injected by Cloud-Init.</summary>
[JsonProperty("ciuser")]
public string? CiUser { get; set; }
/// <summary>Default user password (may be hashed).</summary>
[JsonProperty("cipassword")]
public string? CiPassword { get; set; }
/// <summary>URL-encoded SSH public keys.</summary>
[JsonProperty("sshkeys")]
public string? SshKeys { get; set; }
/// <summary>IP configuration for interface 0 (e.g. "ip=dhcp" or "ip=192.168.1.10/24,gw=192.168.1.1").</summary>
[JsonProperty("ipconfig0")]
public string? IpConfig0 { get; set; }
/// <summary>IP configuration for interface 1.</summary>
[JsonProperty("ipconfig1")]
public string? IpConfig1 { get; set; }
/// <summary>IP configuration for interface 2.</summary>
[JsonProperty("ipconfig2")]
public string? IpConfig2 { get; set; }
/// <summary>IP configuration for interface 3.</summary>
[JsonProperty("ipconfig3")]
public string? IpConfig3 { get; set; }
/// <summary>DNS nameserver(s) space-separated.</summary>
[JsonProperty("nameserver")]
public string? Nameserver { get; set; }
/// <summary>DNS search domain.</summary>
[JsonProperty("searchdomain")]
public string? Searchdomain { get; set; }
/// <summary>Custom Cloud-Init config files (cicustom field).</summary>
[JsonProperty("cicustom")]
public string? CiCustom { get; set; }
public override string ToString() =>
$"CloudInit: User={CiUser ?? "N/A"} | IP0={IpConfig0 ?? "N/A"} | NS={Nameserver ?? "N/A"}";
}
}
@@ -0,0 +1,64 @@
using System;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Vms;
/// <summary>
/// Represents a VM snapshot as returned by the /nodes/{node}/qemu/{vmid}/snapshot endpoint.
/// </summary>
public class PveSnapshot
{
/// <summary>
/// The snapshot name / identifier.
/// </summary>
[JsonPropertyName("name")]
[JsonProperty("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// Optional human-readable description of what this snapshot captures.
/// </summary>
[JsonPropertyName("description")]
[JsonProperty("description")]
public string? Description { get; set; }
/// <summary>
/// Unix timestamp when the snapshot was created.
/// </summary>
[JsonPropertyName("snaptime")]
[JsonProperty("snaptime")]
public long? SnapTime { get; set; }
/// <summary>
/// Indicates whether the VM RAM state was saved with this snapshot (1) or not (0).
/// </summary>
[JsonPropertyName("vmstate")]
[JsonProperty("vmstate")]
public int? VmState { get; set; }
/// <summary>
/// The name of the parent snapshot, or null if this is the root snapshot.
/// </summary>
[JsonPropertyName("parent")]
[JsonProperty("parent")]
public string? Parent { get; set; }
/// <summary>The VM ID this snapshot belongs to. Populated by cmdlets after retrieval.</summary>
public int VmId { get; set; }
/// <summary>The node the VM resides on. Populated by cmdlets after retrieval.</summary>
public string? Node { get; set; }
/// <inheritdoc />
public override string ToString()
{
var timeStr = SnapTime.HasValue
? DateTimeOffset.FromUnixTimeSeconds(SnapTime.Value).ToString("yyyy-MM-dd HH:mm:ss")
: "N/A";
var stateStr = VmState is 1 ? " [+vmstate]" : string.Empty;
var parentStr = Parent is not null ? $" | Parent: {Parent}" : string.Empty;
return $"Snapshot: {Name}{stateStr} | Created: {timeStr} | "
+ $"Description: {Description ?? "(none)"}{parentStr}";
}
}
@@ -0,0 +1,95 @@
using System;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Vms;
/// <summary>
/// Represents a Proxmox VE asynchronous task as returned by the task-related endpoints
/// (e.g., /nodes/{node}/tasks, /nodes/{node}/tasks/{upid}/status).
/// Most mutating API calls return a UPID string that identifies the created task.
/// </summary>
public class PveTask
{
/// <summary>
/// The Unique Process Identifier for this task
/// (e.g., "UPID:pve:000ABC:00000001:5F1234AB:qmstart:100:root@pam:").
/// </summary>
[JsonPropertyName("upid")]
[JsonProperty("upid")]
public string Upid { get; set; } = string.Empty;
/// <summary>
/// The task type string (e.g., "qmstart", "qmstop", "qmmigrate", "vzrestore").
/// </summary>
[JsonPropertyName("type")]
[JsonProperty("type")]
public string? Type { get; set; }
/// <summary>
/// The current task status: "running" while in progress, "stopped" when complete.
/// </summary>
[JsonPropertyName("status")]
[JsonProperty("status")]
public string? Status { get; set; }
/// <summary>
/// The exit status when the task has stopped (e.g., "OK" on success, or an error message).
/// </summary>
[JsonPropertyName("exitstatus")]
[JsonProperty("exitstatus")]
public string? ExitStatus { get; set; }
/// <summary>
/// The node on which this task is executing.
/// </summary>
[JsonPropertyName("node")]
[JsonProperty("node")]
public string? Node { get; set; }
/// <summary>
/// Unix timestamp of when the task started.
/// </summary>
[JsonPropertyName("starttime")]
[JsonProperty("starttime")]
public long? StartTime { get; set; }
/// <summary>
/// Unix timestamp of when the task ended. Null if still running.
/// </summary>
[JsonPropertyName("endtime")]
[JsonProperty("endtime")]
public long? EndTime { get; set; }
/// <summary>
/// The user that initiated the task (e.g., "root@pam").
/// </summary>
[JsonPropertyName("user")]
[JsonProperty("user")]
public string? User { get; set; }
/// <summary>
/// The object ID the task is operating on (e.g., a VM ID or storage name).
/// </summary>
[JsonPropertyName("id")]
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// Returns true when the task completed successfully (stopped with exit status "OK").
/// </summary>
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
public bool IsSuccessful => Status == "stopped" && ExitStatus == "OK";
/// <inheritdoc />
public override string ToString()
{
var startStr = StartTime.HasValue
? DateTimeOffset.FromUnixTimeSeconds(StartTime.Value).ToString("yyyy-MM-dd HH:mm:ss")
: "N/A";
return $"Task [{Type ?? "unknown"}] UPID: {Upid} | Node: {Node ?? "N/A"} | "
+ $"Status: {Status ?? "unknown"} | ExitStatus: {ExitStatus ?? "-"} | "
+ $"User: {User ?? "N/A"} | Started: {startStr}";
}
}
@@ -0,0 +1,21 @@
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Vms
{
/// <summary>
/// Represents a single log line from a Proxmox VE task log,
/// as returned by the /nodes/{node}/tasks/{upid}/log endpoint.
/// </summary>
public class PveTaskLog
{
/// <summary>Line number (1-based).</summary>
[JsonProperty("n")]
public int LineNumber { get; set; }
/// <summary>Log line text.</summary>
[JsonProperty("t")]
public string Text { get; set; } = string.Empty;
public override string ToString() => $"{LineNumber,4}: {Text}";
}
}
+121
View File
@@ -0,0 +1,121 @@
using System;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Vms;
/// <summary>
/// Represents a QEMU/KVM virtual machine as returned by the cluster or node VM list endpoints.
/// </summary>
public class PveVm
{
/// <summary>
/// The unique VM identifier.
/// </summary>
[JsonPropertyName("vmid")]
[JsonProperty("vmid")]
public int VmId { get; set; }
/// <summary>
/// The name of the virtual machine.
/// </summary>
[JsonPropertyName("name")]
[JsonProperty("name")]
public string? Name { get; set; }
/// <summary>
/// The current runtime status of the VM (e.g., "running", "stopped").
/// </summary>
[JsonPropertyName("status")]
[JsonProperty("status")]
public string? Status { get; set; }
/// <summary>
/// The node on which the VM resides.
/// </summary>
[JsonPropertyName("node")]
[JsonProperty("node")]
public string? Node { get; set; }
/// <summary>
/// The number of virtual CPU cores assigned to the VM.
/// </summary>
[JsonPropertyName("cpus")]
[JsonProperty("cpus")]
public int? CpuCount { get; set; }
/// <summary>
/// Maximum memory allocated to the VM, in bytes.
/// </summary>
[JsonPropertyName("maxmem")]
[JsonProperty("maxmem")]
public long? MaxMem { get; set; }
/// <summary>
/// Maximum disk size allocated to the VM, in bytes.
/// </summary>
[JsonPropertyName("maxdisk")]
[JsonProperty("maxdisk")]
public long? MaxDisk { get; set; }
/// <summary>
/// VM uptime in seconds.
/// </summary>
[JsonPropertyName("uptime")]
[JsonProperty("uptime")]
public long? Uptime { get; set; }
/// <summary>
/// Semicolon-separated list of tags assigned to the VM.
/// </summary>
[JsonPropertyName("tags")]
[JsonProperty("tags")]
public string? Tags { get; set; }
/// <summary>
/// Indicates whether the VM is a template (1) or a regular VM (0).
/// </summary>
[JsonPropertyName("template")]
[JsonProperty("template")]
public int? Template { get; set; }
/// <summary>
/// The current lock type applied to the VM, if any (e.g., "migrate", "backup").
/// </summary>
[JsonPropertyName("lock")]
[JsonProperty("lock")]
public string? Lock { get; set; }
/// <summary>
/// The process ID of the running QEMU process, if applicable.
/// </summary>
[JsonPropertyName("pid")]
[JsonProperty("pid")]
public int? Pid { get; set; }
/// <summary>
/// The QMP (QEMU Machine Protocol) status string.
/// </summary>
[JsonPropertyName("qmpstatus")]
[JsonProperty("qmpstatus")]
public string? QmpStatus { get; set; }
/// <summary>
/// Indicates whether the QEMU guest agent is running (non-zero) inside the VM.
/// </summary>
[JsonPropertyName("agent")]
[JsonProperty("agent")]
public int? AgentStatus { get; set; }
/// <inheritdoc />
public override string ToString()
{
var maxMemMb = MaxMem.HasValue ? $"{MaxMem.Value / 1024 / 1024} MB" : "N/A";
var maxDiskGb = MaxDisk.HasValue ? $"{MaxDisk.Value / 1024 / 1024 / 1024} GB" : "N/A";
var uptimeStr = Uptime.HasValue ? TimeSpan.FromSeconds(Uptime.Value).ToString(@"d\.hh\:mm\:ss") : "N/A";
var templateStr = Template is 1 ? " [template]" : string.Empty;
return $"VM {VmId}{templateStr}: {Name ?? "(unnamed)"} | Node: {Node ?? "N/A"} | "
+ $"Status: {Status ?? "N/A"} | CPUs: {CpuCount?.ToString() ?? "N/A"} | "
+ $"Mem: {maxMemMb} | Disk: {maxDiskGb} | Uptime: {uptimeStr}";
}
}
@@ -0,0 +1,353 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Vms;
/// <summary>
/// Represents the configuration of a QEMU/KVM virtual machine,
/// as returned by the /nodes/{node}/qemu/{vmid}/config endpoint.
/// </summary>
public class PveVmConfig
{
// -------------------------------------------------------------------------
// CPU / Memory
// -------------------------------------------------------------------------
/// <summary>
/// Number of CPU cores per socket.
/// </summary>
[JsonPropertyName("cores")]
[JsonProperty("cores")]
public int? Cores { get; set; }
/// <summary>
/// Number of CPU sockets.
/// </summary>
[JsonPropertyName("sockets")]
[JsonProperty("sockets")]
public int? Sockets { get; set; }
/// <summary>
/// Memory size in megabytes.
/// </summary>
[JsonPropertyName("memory")]
[JsonProperty("memory")]
public int? Memory { get; set; }
/// <summary>
/// Emulated CPU type (e.g., "host", "x86-64-v2-AES").
/// </summary>
[JsonPropertyName("cpu")]
[JsonProperty("cpu")]
public string? CpuType { get; set; }
// -------------------------------------------------------------------------
// Firmware / Machine
// -------------------------------------------------------------------------
/// <summary>
/// BIOS implementation to use: "seabios" (default) or "ovmf" (UEFI).
/// </summary>
[JsonPropertyName("bios")]
[JsonProperty("bios")]
public string? Bios { get; set; }
/// <summary>
/// Emulated machine type (e.g., "q35", "i440fx").
/// </summary>
[JsonPropertyName("machine")]
[JsonProperty("machine")]
public string? Machine { get; set; }
// -------------------------------------------------------------------------
// Boot / Args
// -------------------------------------------------------------------------
/// <summary>
/// Boot order specification string.
/// </summary>
[JsonPropertyName("boot")]
[JsonProperty("boot")]
public string? Boot { get; set; }
/// <summary>
/// Arbitrary QEMU command-line arguments appended to the QEMU launch command.
/// </summary>
[JsonPropertyName("args")]
[JsonProperty("args")]
public string? Args { get; set; }
// -------------------------------------------------------------------------
// Metadata
// -------------------------------------------------------------------------
/// <summary>
/// Human-readable description or notes for the VM.
/// </summary>
[JsonPropertyName("description")]
[JsonProperty("description")]
public string? Description { get; set; }
/// <summary>
/// Semicolon-separated list of tags assigned to the VM.
/// </summary>
[JsonPropertyName("tags")]
[JsonProperty("tags")]
public string? Tags { get; set; }
/// <summary>
/// When set to 1, prevents the VM from being deleted or modified accidentally.
/// </summary>
[JsonPropertyName("protection")]
[JsonProperty("protection")]
public int? Protection { get; set; }
/// <summary>
/// NUMA topology enabled (1) or disabled (0).
/// </summary>
[JsonPropertyName("numa")]
[JsonProperty("numa")]
public int? Numa { get; set; }
/// <summary>
/// VirtIO balloon device target memory in MB. 0 disables ballooning.
/// </summary>
[JsonPropertyName("balloon")]
[JsonProperty("balloon")]
public int? Balloon { get; set; }
/// <summary>
/// Guest OS type hint (e.g., "l26" for Linux 2.6+, "win10").
/// </summary>
[JsonPropertyName("ostype")]
[JsonProperty("ostype")]
public string? OsType { get; set; }
// -------------------------------------------------------------------------
// VirtIO disk slots (03, most commonly used)
// -------------------------------------------------------------------------
/// <summary>VirtIO disk slot 0 configuration string.</summary>
[JsonPropertyName("virtio0")]
[JsonProperty("virtio0")]
public string? Virtio0 { get; set; }
/// <summary>VirtIO disk slot 1 configuration string.</summary>
[JsonPropertyName("virtio1")]
[JsonProperty("virtio1")]
public string? Virtio1 { get; set; }
/// <summary>VirtIO disk slot 2 configuration string.</summary>
[JsonPropertyName("virtio2")]
[JsonProperty("virtio2")]
public string? Virtio2 { get; set; }
/// <summary>VirtIO disk slot 3 configuration string.</summary>
[JsonPropertyName("virtio3")]
[JsonProperty("virtio3")]
public string? Virtio3 { get; set; }
// -------------------------------------------------------------------------
// SCSI disk slots (07)
// -------------------------------------------------------------------------
/// <summary>SCSI disk slot 0 configuration string.</summary>
[JsonPropertyName("scsi0")]
[JsonProperty("scsi0")]
public string? Scsi0 { get; set; }
/// <summary>SCSI disk slot 1 configuration string.</summary>
[JsonPropertyName("scsi1")]
[JsonProperty("scsi1")]
public string? Scsi1 { get; set; }
/// <summary>SCSI disk slot 2 configuration string.</summary>
[JsonPropertyName("scsi2")]
[JsonProperty("scsi2")]
public string? Scsi2 { get; set; }
/// <summary>SCSI disk slot 3 configuration string.</summary>
[JsonPropertyName("scsi3")]
[JsonProperty("scsi3")]
public string? Scsi3 { get; set; }
/// <summary>SCSI disk slot 4 configuration string.</summary>
[JsonPropertyName("scsi4")]
[JsonProperty("scsi4")]
public string? Scsi4 { get; set; }
/// <summary>SCSI disk slot 5 configuration string.</summary>
[JsonPropertyName("scsi5")]
[JsonProperty("scsi5")]
public string? Scsi5 { get; set; }
/// <summary>SCSI disk slot 6 configuration string.</summary>
[JsonPropertyName("scsi6")]
[JsonProperty("scsi6")]
public string? Scsi6 { get; set; }
/// <summary>SCSI disk slot 7 configuration string.</summary>
[JsonPropertyName("scsi7")]
[JsonProperty("scsi7")]
public string? Scsi7 { get; set; }
// -------------------------------------------------------------------------
// IDE disk slots (03)
// -------------------------------------------------------------------------
/// <summary>IDE disk/CDROM slot 0 configuration string.</summary>
[JsonPropertyName("ide0")]
[JsonProperty("ide0")]
public string? Ide0 { get; set; }
/// <summary>IDE disk/CDROM slot 1 configuration string.</summary>
[JsonPropertyName("ide1")]
[JsonProperty("ide1")]
public string? Ide1 { get; set; }
/// <summary>IDE disk/CDROM slot 2 configuration string.</summary>
[JsonPropertyName("ide2")]
[JsonProperty("ide2")]
public string? Ide2 { get; set; }
/// <summary>IDE disk/CDROM slot 3 configuration string.</summary>
[JsonPropertyName("ide3")]
[JsonProperty("ide3")]
public string? Ide3 { get; set; }
// -------------------------------------------------------------------------
// SATA disk slots (05)
// -------------------------------------------------------------------------
/// <summary>SATA disk slot 0 configuration string.</summary>
[JsonPropertyName("sata0")]
[JsonProperty("sata0")]
public string? Sata0 { get; set; }
/// <summary>SATA disk slot 1 configuration string.</summary>
[JsonPropertyName("sata1")]
[JsonProperty("sata1")]
public string? Sata1 { get; set; }
/// <summary>SATA disk slot 2 configuration string.</summary>
[JsonPropertyName("sata2")]
[JsonProperty("sata2")]
public string? Sata2 { get; set; }
/// <summary>SATA disk slot 3 configuration string.</summary>
[JsonPropertyName("sata3")]
[JsonProperty("sata3")]
public string? Sata3 { get; set; }
/// <summary>SATA disk slot 4 configuration string.</summary>
[JsonPropertyName("sata4")]
[JsonProperty("sata4")]
public string? Sata4 { get; set; }
/// <summary>SATA disk slot 5 configuration string.</summary>
[JsonPropertyName("sata5")]
[JsonProperty("sata5")]
public string? Sata5 { get; set; }
// -------------------------------------------------------------------------
// Network interface slots (07)
// -------------------------------------------------------------------------
/// <summary>Network interface 0 configuration string (e.g., "virtio=XX:XX:XX:XX:XX:XX,bridge=vmbr0").</summary>
[JsonPropertyName("net0")]
[JsonProperty("net0")]
public string? Net0 { get; set; }
/// <summary>Network interface 1 configuration string.</summary>
[JsonPropertyName("net1")]
[JsonProperty("net1")]
public string? Net1 { get; set; }
/// <summary>Network interface 2 configuration string.</summary>
[JsonPropertyName("net2")]
[JsonProperty("net2")]
public string? Net2 { get; set; }
/// <summary>Network interface 3 configuration string.</summary>
[JsonPropertyName("net3")]
[JsonProperty("net3")]
public string? Net3 { get; set; }
/// <summary>Network interface 4 configuration string.</summary>
[JsonPropertyName("net4")]
[JsonProperty("net4")]
public string? Net4 { get; set; }
/// <summary>Network interface 5 configuration string.</summary>
[JsonPropertyName("net5")]
[JsonProperty("net5")]
public string? Net5 { get; set; }
/// <summary>Network interface 6 configuration string.</summary>
[JsonPropertyName("net6")]
[JsonProperty("net6")]
public string? Net6 { get; set; }
/// <summary>Network interface 7 configuration string.</summary>
[JsonPropertyName("net7")]
[JsonProperty("net7")]
public string? Net7 { get; set; }
// -------------------------------------------------------------------------
// Cloud-Init
// -------------------------------------------------------------------------
/// <summary>Cloud-Init default user name.</summary>
[JsonPropertyName("ciuser")]
[JsonProperty("ciuser")]
public string? CiUser { get; set; }
/// <summary>Cloud-Init default user password (hashed or plaintext depending on PVE version).</summary>
[JsonPropertyName("cipassword")]
[JsonProperty("cipassword")]
public string? CiPassword { get; set; }
/// <summary>URL-encoded SSH public keys injected by Cloud-Init.</summary>
[JsonPropertyName("sshkeys")]
[JsonProperty("sshkeys")]
public string? SshKeys { get; set; }
/// <summary>Cloud-Init IP configuration for interface 0.</summary>
[JsonPropertyName("ipconfig0")]
[JsonProperty("ipconfig0")]
public string? IpConfig0 { get; set; }
/// <summary>Cloud-Init IP configuration for interface 1.</summary>
[JsonPropertyName("ipconfig1")]
[JsonProperty("ipconfig1")]
public string? IpConfig1 { get; set; }
/// <summary>Cloud-Init IP configuration for interface 2.</summary>
[JsonPropertyName("ipconfig2")]
[JsonProperty("ipconfig2")]
public string? IpConfig2 { get; set; }
/// <summary>Cloud-Init IP configuration for interface 3.</summary>
[JsonPropertyName("ipconfig3")]
[JsonProperty("ipconfig3")]
public string? IpConfig3 { get; set; }
/// <summary>DNS nameserver(s) injected via Cloud-Init.</summary>
[JsonPropertyName("nameserver")]
[JsonProperty("nameserver")]
public string? Nameserver { get; set; }
/// <summary>DNS search domain injected via Cloud-Init.</summary>
[JsonPropertyName("searchdomain")]
[JsonProperty("searchdomain")]
public string? Searchdomain { get; set; }
/// <inheritdoc />
public override string ToString()
{
var totalCores = (Cores ?? 1) * (Sockets ?? 1);
return $"Config | CPUs: {totalCores} ({Sockets ?? 1}S x {Cores ?? 1}C) | "
+ $"Memory: {Memory?.ToString() ?? "N/A"} MB | BIOS: {Bios ?? "seabios"} | "
+ $"Machine: {Machine ?? "default"} | OS: {OsType ?? "N/A"}";
}
}