mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-26 07:58:14 +00:00
feat: close API coverage gaps — 51 new cmdlets across 6 areas
Phase 1 — Missing Update/Set operations (10 cmdlets): - SDN: Set-PveSdnZone/Vnet/Subnet/Controller/Ipam/Dns + Invoke-PveSdnApply - Set-PveRole, Set-PveStorage, Set-PveApiToken Phase 2 — Cluster operations (8 cmdlets): - Get-PveClusterResource (cluster-wide inventory) - Get-PveTaskList, Stop-PveTask - Get/New/Set/Remove-PvePool - Get-PveBackupInfo (unprotected VMs) Phase 3 — VM & container gaps (14 cmdlets): - Move-PveVmDisk, Remove-PveVmDisk - Guest agent: Get-PveVmGuestOsInfo/FsInfo, Read/Write-PveVmGuestFile, Set-PveVmGuestPassword, Invoke-PveVmGuestFsTrim - Suspend/Resume-PveContainer, Resize-PveContainerDisk, New-PveContainerTemplate, Move-PveContainerVolume, Get-PveContainerInterface Phase 4 — Storage content management (4 cmdlets): - Get-PveStorageStatus, Remove/Set-PveStorageContent, New-PveStorageDisk Phase 5 — Node operations (6 cmdlets): - Get/Set-PveNodeConfig, Get/Set-PveNodeDns, Start/Stop-PveNodeVms Phase 6 — Access management (9 cmdlets): - Get/New/Set/Remove-PveGroup, Get/New/Set/Remove-PveDomain, Set-PvePassword Total: 169 cmdlets (was 118). Includes models, services, format views, Pester unit tests, manifest updates, README, CHANGELOG, API coverage docs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,19 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi
|
||||
- SDN Controller cmdlets (3): Get/New/Remove-PveSdnController for controller management
|
||||
- PSGallery version badge in README
|
||||
- Integration tests for firewall rules, aliases, IP sets, backup jobs, and OVA import
|
||||
- SDN Update cmdlets (7): Set-PveSdnZone/Vnet/Subnet/Controller/Ipam/Dns + Invoke-PveSdnApply
|
||||
- Set-PveRole, Set-PveStorage, Set-PveApiToken for missing update operations
|
||||
- Get-PveClusterResource: single-call cluster-wide inventory of all VMs, containers, nodes, storage
|
||||
- Task management: Get-PveTaskList (list tasks on node), Stop-PveTask (cancel running tasks)
|
||||
- Pool management cmdlets (4): Get/New/Set/Remove-PvePool
|
||||
- Get-PveBackupInfo: find VMs/containers not covered by backup jobs
|
||||
- VM disk operations: Move-PveVmDisk (storage migration), Remove-PveVmDisk (detach/delete)
|
||||
- Guest agent extensions (6): Get-PveVmGuestOsInfo, Get-PveVmGuestFsInfo, Read/Write-PveVmGuestFile, Set-PveVmGuestPassword, Invoke-PveVmGuestFsTrim
|
||||
- Container gaps (6): Suspend/Resume-PveContainer, Resize-PveContainerDisk, New-PveContainerTemplate, Move-PveContainerVolume, Get-PveContainerInterface
|
||||
- Storage content management (4): Get-PveStorageStatus, Remove/Set-PveStorageContent, New-PveStorageDisk
|
||||
- Node operations (6): Get/Set-PveNodeConfig, Get/Set-PveNodeDns, Start/Stop-PveNodeVms
|
||||
- Access management (9): Get/New/Set/Remove-PveGroup, Get/New/Set/Remove-PveDomain, Set-PvePassword
|
||||
- Two-tier version gating: introduced vs default version with clear user messaging
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -359,19 +359,100 @@ SDN management requires Proxmox VE 8.0 or later. Connected server is version 7.4
|
||||
| `New-PveBackupJob` | Create a scheduled backup job |
|
||||
| `Set-PveBackupJob` | Update a scheduled backup job |
|
||||
| `Remove-PveBackupJob` | Delete a scheduled backup job |
|
||||
| `Get-PveBackupInfo` | Find VMs/containers not covered by backup jobs |
|
||||
|
||||
### SDN — IPAM / DNS / Controllers (PVE 8.0+)
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `Get-PveSdnIpam` | List SDN IPAM plugins |
|
||||
| `New-PveSdnIpam` | Create an SDN IPAM plugin |
|
||||
| `Set-PveSdnIpam` | Update an SDN IPAM plugin |
|
||||
| `Remove-PveSdnIpam` | Remove an SDN IPAM plugin |
|
||||
| `Get-PveSdnDns` | List SDN DNS plugins |
|
||||
| `New-PveSdnDns` | Create an SDN DNS plugin |
|
||||
| `Set-PveSdnDns` | Update an SDN DNS plugin |
|
||||
| `Remove-PveSdnDns` | Remove an SDN DNS plugin |
|
||||
| `Get-PveSdnController` | List SDN controllers |
|
||||
| `New-PveSdnController` | Create an SDN controller |
|
||||
| `Set-PveSdnController` | Update an SDN controller |
|
||||
| `Remove-PveSdnController` | Remove an SDN controller |
|
||||
| `Invoke-PveSdnApply` | Apply pending SDN configuration changes |
|
||||
| `Set-PveSdnZone` | Update an SDN zone |
|
||||
| `Set-PveSdnVnet` | Update an SDN VNet |
|
||||
| `Set-PveSdnSubnet` | Update an SDN subnet |
|
||||
|
||||
### Cluster
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `Get-PveClusterResource` | List all resources (VMs, containers, nodes, storage) cluster-wide |
|
||||
|
||||
### Pools
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `Get-PvePool` | List resource pools |
|
||||
| `New-PvePool` | Create a resource pool |
|
||||
| `Set-PvePool` | Update a resource pool |
|
||||
| `Remove-PvePool` | Delete a resource pool |
|
||||
|
||||
### VM Disk Operations
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `Move-PveVmDisk` | Move a VM disk to a different storage |
|
||||
| `Remove-PveVmDisk` | Detach and optionally delete a VM disk |
|
||||
|
||||
### Guest Agent Extensions
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `Get-PveVmGuestOsInfo` | Get guest OS information via QEMU agent |
|
||||
| `Get-PveVmGuestFsInfo` | Get guest filesystem information |
|
||||
| `Read-PveVmGuestFile` | Read a file from inside a guest VM |
|
||||
| `Write-PveVmGuestFile` | Write a file inside a guest VM |
|
||||
| `Set-PveVmGuestPassword` | Change a user password inside a guest VM |
|
||||
| `Invoke-PveVmGuestFsTrim` | TRIM guest VM filesystems |
|
||||
|
||||
### Additional Container Operations
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `Suspend-PveContainer` | Suspend (freeze) a container |
|
||||
| `Resume-PveContainer` | Resume a suspended container |
|
||||
| `Resize-PveContainerDisk` | Resize a container disk/volume |
|
||||
| `New-PveContainerTemplate` | Convert a container to a template |
|
||||
| `Move-PveContainerVolume` | Move a container volume to a different storage |
|
||||
| `Get-PveContainerInterface` | Get container network interfaces |
|
||||
|
||||
### Storage Content
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `Get-PveStorageStatus` | Get storage usage statistics |
|
||||
| `Remove-PveStorageContent` | Delete a volume, backup, or ISO from storage |
|
||||
| `Set-PveStorageContent` | Update volume notes/properties |
|
||||
| `New-PveStorageDisk` | Allocate a new empty disk image |
|
||||
|
||||
### Node Operations
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `Get-PveNodeConfig` | Get node configuration |
|
||||
| `Set-PveNodeConfig` | Update node configuration |
|
||||
| `Get-PveNodeDns` | Get node DNS configuration |
|
||||
| `Set-PveNodeDns` | Update node DNS configuration |
|
||||
| `Start-PveNodeVms` | Start all VMs on a node |
|
||||
| `Stop-PveNodeVms` | Stop all VMs on a node |
|
||||
|
||||
### Access — Groups & Domains
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `Get-PveGroup` | List user groups |
|
||||
| `New-PveGroup` | Create a user group |
|
||||
| `Set-PveGroup` | Update a user group |
|
||||
| `Remove-PveGroup` | Delete a user group |
|
||||
| `Get-PveDomain` | List authentication realms (PAM, LDAP, AD, OpenID) |
|
||||
| `New-PveDomain` | Create an authentication realm |
|
||||
| `Set-PveDomain` | Update an authentication realm |
|
||||
| `Remove-PveDomain` | Delete an authentication realm |
|
||||
| `Set-PvePassword` | Change a user's password |
|
||||
| `Set-PveRole` | Update a role's privileges |
|
||||
| `Set-PveStorage` | Update a storage definition |
|
||||
| `Set-PveApiToken` | Update an API token |
|
||||
|
||||
## Known Limitations (v1)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ This document tracks which PVE API areas are implemented in PSProxmoxVE and whic
|
||||
|
||||
**Last updated:** 2026-03-21
|
||||
**Module version:** 0.1.0-preview
|
||||
**Total cmdlets:** 110
|
||||
**Total cmdlets:** 169
|
||||
|
||||
## Implemented
|
||||
|
||||
@@ -27,12 +27,26 @@ This document tracks which PVE API areas are implemented in PSProxmoxVE and whic
|
||||
| **API Tokens** | 3 | `/access/users/{userid}/tokens/*` (CRUD) |
|
||||
| **Templates** | 4 | VM template conversion, listing, cloning, removal |
|
||||
| **Cloud-Init** | 3 | `/nodes/{node}/qemu/{vmid}/config` (cloud-init fields), `/nodes/{node}/qemu/{vmid}/cloudinit/regenerate` |
|
||||
| **Tasks** | 2 | `/nodes/{node}/tasks/{upid}/status` (get, wait) |
|
||||
| **Tasks** | 4 | `/nodes/{node}/tasks` (list, get status, stop, wait) |
|
||||
| **Firewall** | 21 | `/cluster/firewall/*`, `/nodes/{node}/firewall/*`, `/nodes/{node}/qemu/{vmid}/firewall/*`, `/nodes/{node}/lxc/{vmid}/firewall/*` (rules, groups, aliases, IP sets, options, refs) |
|
||||
| **Backup** | 5 | `/nodes/{node}/vzdump`, `/cluster/backup/*` (ad-hoc backup, scheduled job CRUD) |
|
||||
| **SDN IPAM** | 3 | `/cluster/sdn/ipams/*` (CRUD) |
|
||||
| **SDN DNS** | 3 | `/cluster/sdn/dns/*` (CRUD) |
|
||||
| **SDN Controllers** | 3 | `/cluster/sdn/controllers/*` (CRUD) |
|
||||
| **Backup** | 7 | `/nodes/{node}/vzdump`, `/cluster/backup/*`, `/cluster/backup-info/not-backed-up` |
|
||||
| **SDN Zones** | 4 | `/cluster/sdn/zones/*` (CRUD + update) |
|
||||
| **SDN VNets** | 4 | `/cluster/sdn/vnets/*` (CRUD + update) |
|
||||
| **SDN Subnets** | 4 | `/cluster/sdn/vnets/{vnet}/subnets/*` (CRUD + update) |
|
||||
| **SDN IPAM** | 4 | `/cluster/sdn/ipams/*` (CRUD + update) |
|
||||
| **SDN DNS** | 4 | `/cluster/sdn/dns/*` (CRUD + update) |
|
||||
| **SDN Controllers** | 4 | `/cluster/sdn/controllers/*` (CRUD + update) |
|
||||
| **SDN Apply** | 1 | `PUT /cluster/sdn` (apply pending changes) |
|
||||
| **Cluster** | 1 | `GET /cluster/resources` (cluster-wide inventory) |
|
||||
| **Pools** | 4 | `/pools/*` (CRUD) |
|
||||
| **VM Disk Ops** | 2 | `move_disk`, `unlink` |
|
||||
| **Guest Agent (ext)** | 6 | `get-osinfo`, `get-fsinfo`, `file-read`, `file-write`, `set-user-password`, `fstrim` |
|
||||
| **Container Ops** | 6 | `suspend`, `resume`, `resize`, `template`, `move_volume`, `interfaces` |
|
||||
| **Storage Content** | 4 | `status`, `content` DELETE/PUT/POST |
|
||||
| **Node Ops** | 6 | `config`, `dns`, `startall`, `stopall` |
|
||||
| **Access Groups** | 4 | `/access/groups/*` (CRUD) |
|
||||
| **Access Domains** | 4 | `/access/domains/*` (CRUD) |
|
||||
| **Access Password** | 1 | `PUT /access/password` |
|
||||
|
||||
## Not Yet Implemented
|
||||
|
||||
@@ -40,7 +54,6 @@ This document tracks which PVE API areas are implemented in PSProxmoxVE and whic
|
||||
|
||||
| Area | Key Endpoints | Notes | Priority |
|
||||
|------|--------------|-------|----------|
|
||||
| **Pool Management** | `/pools/*` | Multi-tenant environments, resource grouping | Medium |
|
||||
|
||||
### Lower Priority Gaps
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Cluster;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a resource entry returned by the /cluster/resources endpoint.
|
||||
/// Resources can be VMs, containers, nodes, storage, or SDN objects.
|
||||
/// </summary>
|
||||
public class PveClusterResource
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique resource identifier (e.g., "node/pve1", "qemu/100", "storage/local").
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
[JsonProperty("id")]
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The resource type: "vm" (QEMU), "lxc" (container), "node", "storage", or "sdn".
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current status of the resource (e.g., "running", "stopped", "online").
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
[JsonProperty("status")]
|
||||
public string? Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The display name of the resource.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
[JsonProperty("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The node on which this resource resides.
|
||||
/// </summary>
|
||||
[JsonPropertyName("node")]
|
||||
[JsonProperty("node")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The pool this resource belongs to, if any.
|
||||
/// </summary>
|
||||
[JsonPropertyName("pool")]
|
||||
[JsonProperty("pool")]
|
||||
public string? Pool { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Uptime in seconds.
|
||||
/// </summary>
|
||||
[JsonPropertyName("uptime")]
|
||||
[JsonProperty("uptime")]
|
||||
public long? Uptime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of CPUs/cores available to this resource.
|
||||
/// </summary>
|
||||
[JsonPropertyName("maxcpu")]
|
||||
[JsonProperty("maxcpu")]
|
||||
public double? MaxCpu { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current CPU usage as a fraction (0.0 to 1.0).
|
||||
/// </summary>
|
||||
[JsonPropertyName("cpu")]
|
||||
[JsonProperty("cpu")]
|
||||
public double? Cpu { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum memory in bytes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("maxmem")]
|
||||
[JsonProperty("maxmem")]
|
||||
public long? MaxMem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current memory usage in bytes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("mem")]
|
||||
[JsonProperty("mem")]
|
||||
public long? Mem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum disk space in bytes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("maxdisk")]
|
||||
[JsonProperty("maxdisk")]
|
||||
public long? MaxDisk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current disk usage in bytes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("disk")]
|
||||
[JsonProperty("disk")]
|
||||
public long? Disk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this resource is a template (1) or not (0). Only for VM/LXC types.
|
||||
/// </summary>
|
||||
[JsonPropertyName("template")]
|
||||
[JsonProperty("template")]
|
||||
public int? Template { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The VM or container ID. Only for VM/LXC types.
|
||||
/// </summary>
|
||||
[JsonPropertyName("vmid")]
|
||||
[JsonProperty("vmid")]
|
||||
public int? VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HA (High Availability) state of the resource, if managed by HA.
|
||||
/// </summary>
|
||||
[JsonPropertyName("hastate")]
|
||||
[JsonProperty("hastate")]
|
||||
public string? HaState { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Type ?? "unknown"}: {Name ?? Id ?? "N/A"} | Node: {Node ?? "N/A"} | Status: {Status ?? "N/A"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Cluster;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Proxmox VE resource pool as returned by the /pools endpoint.
|
||||
/// </summary>
|
||||
public class PvePool
|
||||
{
|
||||
/// <summary>
|
||||
/// The pool identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("poolid")]
|
||||
[JsonProperty("poolid")]
|
||||
public string? PoolId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional comment or description for the pool.
|
||||
/// </summary>
|
||||
[JsonPropertyName("comment")]
|
||||
[JsonProperty("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The pool members (VMs, containers, storage). Returned as a raw array
|
||||
/// when querying a specific pool.
|
||||
/// </summary>
|
||||
[JsonPropertyName("members")]
|
||||
[JsonProperty("members")]
|
||||
public JArray? Members { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var memberCount = Members?.Count ?? 0;
|
||||
return $"Pool: {PoolId ?? "N/A"} | Comment: {Comment ?? "-"} | Members: {memberCount}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Containers;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a network interface on an LXC container.
|
||||
/// </summary>
|
||||
public class PveContainerInterface
|
||||
{
|
||||
/// <summary>The interface name (e.g., "eth0", "lo").</summary>
|
||||
[JsonPropertyName("name")]
|
||||
[JsonProperty("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>The hardware (MAC) address of the interface.</summary>
|
||||
[JsonPropertyName("hwaddr")]
|
||||
[JsonProperty("hwaddr")]
|
||||
public string? HwAddr { get; set; }
|
||||
|
||||
/// <summary>The IPv4 address assigned to this interface.</summary>
|
||||
[JsonPropertyName("inet")]
|
||||
[JsonProperty("inet")]
|
||||
public string? Inet { get; set; }
|
||||
|
||||
/// <summary>The IPv6 address assigned to this interface.</summary>
|
||||
[JsonPropertyName("inet6")]
|
||||
[JsonProperty("inet6")]
|
||||
public string? Inet6 { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var addr = Inet ?? Inet6 ?? "no address";
|
||||
return $"{Name ?? "Unknown"} ({HwAddr ?? "N/A"}): {addr}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the status of a Proxmox VE storage on a specific node,
|
||||
/// as returned by the /nodes/{node}/storage/{storage}/status endpoint.
|
||||
/// </summary>
|
||||
public class PveStorageStatus
|
||||
{
|
||||
/// <summary>Total capacity in bytes.</summary>
|
||||
[JsonPropertyName("total")]
|
||||
[JsonProperty("total")]
|
||||
public long? Total { get; set; }
|
||||
|
||||
/// <summary>Used capacity in bytes.</summary>
|
||||
[JsonPropertyName("used")]
|
||||
[JsonProperty("used")]
|
||||
public long? Used { get; set; }
|
||||
|
||||
/// <summary>Available (free) capacity in bytes.</summary>
|
||||
[JsonPropertyName("avail")]
|
||||
[JsonProperty("avail")]
|
||||
public long? Available { get; set; }
|
||||
|
||||
/// <summary>Whether the storage is currently active (1) or not (0).</summary>
|
||||
[JsonPropertyName("active")]
|
||||
[JsonProperty("active")]
|
||||
public int? Active { get; set; }
|
||||
|
||||
/// <summary>Comma-separated list of supported content types.</summary>
|
||||
[JsonPropertyName("content")]
|
||||
[JsonProperty("content")]
|
||||
public string? Content { get; set; }
|
||||
|
||||
/// <summary>Whether the storage is enabled (1) or disabled (0).</summary>
|
||||
[JsonPropertyName("enabled")]
|
||||
[JsonProperty("enabled")]
|
||||
public int? Enabled { get; set; }
|
||||
|
||||
/// <summary>Whether the storage is shared across cluster nodes (1) or node-local (0).</summary>
|
||||
[JsonPropertyName("shared")]
|
||||
[JsonProperty("shared")]
|
||||
public int? Shared { get; set; }
|
||||
|
||||
/// <summary>The storage backend type.</summary>
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>The storage identifier. Populated by the cmdlet after retrieval.</summary>
|
||||
public string? Storage { get; set; }
|
||||
|
||||
/// <summary>The node name. Populated by the cmdlet after retrieval.</summary>
|
||||
public string? Node { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var usedGb = Used.HasValue ? $"{Used.Value / 1024.0 / 1024 / 1024:F1} GB" : "N/A";
|
||||
var totalGb = Total.HasValue ? $"{Total.Value / 1024.0 / 1024 / 1024:F1} GB" : "N/A";
|
||||
var availGb = Available.HasValue ? $"{Available.Value / 1024.0 / 1024 / 1024:F1} GB" : "N/A";
|
||||
var activeStr = Active is 1 ? "active" : "inactive";
|
||||
return $"Storage: {Storage ?? "N/A"} | Type: {Type ?? "N/A"} | {activeStr} | "
|
||||
+ $"Used: {usedGb}/{totalGb} | Free: {availGb}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Users;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Proxmox VE authentication domain/realm as returned by the /access/domains endpoint.
|
||||
/// </summary>
|
||||
public class PveDomain
|
||||
{
|
||||
/// <summary>The realm identifier (e.g., "pam", "pve", "myldap").</summary>
|
||||
[JsonPropertyName("realm")]
|
||||
[JsonProperty("realm")]
|
||||
public string Realm { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The domain type (e.g., "pam", "pve", "ad", "ldap", "openid").</summary>
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Optional comment/description for the domain.</summary>
|
||||
[JsonPropertyName("comment")]
|
||||
[JsonProperty("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>Whether this is the default realm (1) or not (0).</summary>
|
||||
[JsonPropertyName("default")]
|
||||
[JsonProperty("default")]
|
||||
public int? Default { get; set; }
|
||||
|
||||
/// <summary>TFA configuration string, if any.</summary>
|
||||
[JsonPropertyName("tfa")]
|
||||
[JsonProperty("tfa")]
|
||||
public string? Tfa { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var defaultStr = Default is 1 ? " [default]" : string.Empty;
|
||||
return $"Realm: {Realm}{defaultStr} | Type: {Type} | Comment: {Comment ?? "N/A"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Users;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Proxmox VE group as returned by the /access/groups endpoint.
|
||||
/// </summary>
|
||||
public class PveGroup
|
||||
{
|
||||
/// <summary>The unique group identifier.</summary>
|
||||
[JsonPropertyName("groupid")]
|
||||
[JsonProperty("groupid")]
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Optional comment/description for the group.</summary>
|
||||
[JsonPropertyName("comment")]
|
||||
[JsonProperty("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>Comma-separated list of user IDs that belong to this group.</summary>
|
||||
[JsonPropertyName("users")]
|
||||
[JsonProperty("users")]
|
||||
public string? Users { get; set; }
|
||||
|
||||
/// <summary>Array of member user IDs. Populated when available.</summary>
|
||||
[JsonPropertyName("members")]
|
||||
[JsonProperty("members")]
|
||||
public string[]? Members { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Group: {GroupId} | Comment: {Comment ?? "N/A"} | Users: {Users ?? "none"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Vms;
|
||||
|
||||
/// <summary>
|
||||
/// Represents filesystem information reported by the QEMU guest agent.
|
||||
/// </summary>
|
||||
public class PveGuestFsInfo
|
||||
{
|
||||
/// <summary>The filesystem name/device.</summary>
|
||||
[JsonPropertyName("name")]
|
||||
[JsonProperty("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>The mount point path.</summary>
|
||||
[JsonPropertyName("mountpoint")]
|
||||
[JsonProperty("mountpoint")]
|
||||
public string? MountPoint { get; set; }
|
||||
|
||||
/// <summary>The filesystem type (e.g., "ext4", "ntfs").</summary>
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>Total size of the filesystem in bytes.</summary>
|
||||
[JsonPropertyName("total-bytes")]
|
||||
[JsonProperty("total-bytes")]
|
||||
public long? TotalBytes { get; set; }
|
||||
|
||||
/// <summary>Used space on the filesystem in bytes.</summary>
|
||||
[JsonPropertyName("used-bytes")]
|
||||
[JsonProperty("used-bytes")]
|
||||
public long? UsedBytes { get; set; }
|
||||
|
||||
/// <summary>List of disk devices backing this filesystem.</summary>
|
||||
[JsonPropertyName("disk")]
|
||||
[JsonProperty("disk")]
|
||||
public string[]? DiskList { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var used = UsedBytes.HasValue && TotalBytes.HasValue && TotalBytes.Value > 0
|
||||
? $" ({UsedBytes.Value * 100 / TotalBytes.Value}% used)"
|
||||
: string.Empty;
|
||||
return $"{MountPoint ?? Name ?? "Unknown"} [{Type ?? "?"}]{used}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Vms;
|
||||
|
||||
/// <summary>
|
||||
/// Represents OS information reported by the QEMU guest agent.
|
||||
/// </summary>
|
||||
public class PveGuestOsInfo
|
||||
{
|
||||
/// <summary>The OS identifier (e.g., "mswindows", "linux").</summary>
|
||||
[JsonPropertyName("id")]
|
||||
[JsonProperty("id")]
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <summary>The OS name (e.g., "Microsoft Windows 10 Enterprise").</summary>
|
||||
[JsonPropertyName("name")]
|
||||
[JsonProperty("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>The kernel release string.</summary>
|
||||
[JsonPropertyName("kernel-release")]
|
||||
[JsonProperty("kernel-release")]
|
||||
public string? KernelRelease { get; set; }
|
||||
|
||||
/// <summary>The kernel version string.</summary>
|
||||
[JsonPropertyName("kernel-version")]
|
||||
[JsonProperty("kernel-version")]
|
||||
public string? KernelVersion { get; set; }
|
||||
|
||||
/// <summary>The machine hardware name.</summary>
|
||||
[JsonPropertyName("machine")]
|
||||
[JsonProperty("machine")]
|
||||
public string? Machine { get; set; }
|
||||
|
||||
/// <summary>The OS version details.</summary>
|
||||
[JsonPropertyName("version")]
|
||||
[JsonProperty("version")]
|
||||
public PveGuestOsVersion? Version { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var ver = Version != null ? $" {Version.Major}.{Version.Minor}" : string.Empty;
|
||||
return $"{Name ?? Id ?? "Unknown"}{ver} ({KernelRelease ?? "N/A"})";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a guest OS version with major and minor components.
|
||||
/// </summary>
|
||||
public class PveGuestOsVersion
|
||||
{
|
||||
/// <summary>The version identifier string.</summary>
|
||||
[JsonPropertyName("id")]
|
||||
[JsonProperty("id")]
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <summary>The major version number.</summary>
|
||||
[JsonPropertyName("major")]
|
||||
[JsonProperty("major")]
|
||||
public int? Major { get; set; }
|
||||
|
||||
/// <summary>The minor version number.</summary>
|
||||
[JsonPropertyName("minor")]
|
||||
[JsonProperty("minor")]
|
||||
public int? Minor { get; set; }
|
||||
}
|
||||
@@ -106,6 +106,24 @@ namespace PSProxmoxVE.Core.Services
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Backup compliance info
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns the list of guests not covered by any backup job.
|
||||
/// </summary>
|
||||
public JArray GetNotBackedUp(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync("cluster/backup-info/not-backed-up")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data as JArray ?? new JArray();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -24,5 +24,24 @@ namespace PSProxmoxVE.Core.Services
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveClusterStatus[]>() ?? Array.Empty<PveClusterStatus>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns cluster resources, optionally filtered by type.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="type">Optional resource type filter (vm, lxc, node, storage, sdn).</param>
|
||||
public PveClusterResource[] GetClusterResources(PveSession session, string? type = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var resource = "cluster/resources";
|
||||
if (!string.IsNullOrEmpty(type))
|
||||
resource += $"?type={Uri.EscapeDataString(type!)}";
|
||||
|
||||
var response = client.GetAsync(resource).GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveClusterResource[]>() ?? Array.Empty<PveClusterResource>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +318,104 @@ namespace PSProxmoxVE.Core.Services
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Suspend / Resume
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Suspends a container. Returns the task UPID.</summary>
|
||||
public PveTask SuspendContainer(PveSession session, string node, int vmid)
|
||||
=> PostStatus(session, node, vmid, "suspend");
|
||||
|
||||
/// <summary>Resumes a suspended container. Returns the task UPID.</summary>
|
||||
public PveTask ResumeContainer(PveSession session, string node, int vmid)
|
||||
=> PostStatus(session, node, vmid, "resume");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Disk / volume operations
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Resizes a container disk/volume. Returns the task UPID.
|
||||
/// </summary>
|
||||
public PveTask ResizeContainerDisk(PveSession session, string node, int vmid, string disk, string size)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(disk)) throw new ArgumentNullException(nameof(disk));
|
||||
if (string.IsNullOrWhiteSpace(size)) throw new ArgumentNullException(nameof(size));
|
||||
|
||||
var formData = new Dictionary<string, string>
|
||||
{
|
||||
["disk"] = disk,
|
||||
["size"] = size
|
||||
};
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.PutAsync($"nodes/{node}/lxc/{vmid}/resize", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves a container volume to a different storage. Returns the task UPID.
|
||||
/// </summary>
|
||||
public PveTask MoveVolume(PveSession session, string node, int vmid, string volume, string storage, bool delete = true)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(volume)) throw new ArgumentNullException(nameof(volume));
|
||||
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
|
||||
|
||||
var formData = new Dictionary<string, string>
|
||||
{
|
||||
["volume"] = volume,
|
||||
["storage"] = storage,
|
||||
["delete"] = delete ? "1" : "0"
|
||||
};
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.PostAsync($"nodes/{node}/lxc/{vmid}/move_volume", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Template
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Converts a container to a template. This is irreversible. Returns the task UPID.
|
||||
/// </summary>
|
||||
public PveTask ConvertToTemplate(PveSession session, string node, int vmid)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.PostAsync($"nodes/{node}/lxc/{vmid}/template")
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Interfaces
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns network interface information for a container.
|
||||
/// </summary>
|
||||
public PveContainerInterface[] GetInterfaces(PveSession session, string node, int vmid)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"nodes/{node}/lxc/{vmid}/interfaces")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveContainerInterface[]>() ?? Array.Empty<PveContainerInterface>();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -438,6 +438,108 @@ namespace PSProxmoxVE.Core.Services
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// SDN Update operations
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Applies pending SDN configuration changes cluster-wide.
|
||||
/// </summary>
|
||||
public void ApplySdn(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync("cluster/sdn", new Dictionary<string, string>())
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an SDN zone configuration.
|
||||
/// </summary>
|
||||
public void UpdateSdnZone(PveSession session, string zone, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(zone)) throw new ArgumentNullException(nameof(zone));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"cluster/sdn/zones/{Uri.EscapeDataString(zone)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an SDN VNet configuration.
|
||||
/// </summary>
|
||||
public void UpdateSdnVnet(PveSession session, string vnet, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an SDN subnet configuration.
|
||||
/// </summary>
|
||||
public void UpdateSdnSubnet(PveSession session, string vnet, string subnet, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
|
||||
if (string.IsNullOrWhiteSpace(subnet)) throw new ArgumentNullException(nameof(subnet));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync(
|
||||
$"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets/{Uri.EscapeDataString(subnet)}",
|
||||
config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an SDN controller configuration.
|
||||
/// </summary>
|
||||
public void UpdateSdnController(PveSession session, string controller, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(controller)) throw new ArgumentNullException(nameof(controller));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"cluster/sdn/controllers/{Uri.EscapeDataString(controller)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an SDN IPAM plugin configuration.
|
||||
/// </summary>
|
||||
public void UpdateSdnIpam(PveSession session, string ipam, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(ipam)) throw new ArgumentNullException(nameof(ipam));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"cluster/sdn/ipams/{Uri.EscapeDataString(ipam)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an SDN DNS plugin configuration.
|
||||
/// </summary>
|
||||
public void UpdateSdnDns(PveSession session, string dns, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(dns)) throw new ArgumentNullException(nameof(dns));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"cluster/sdn/dns/{Uri.EscapeDataString(dns)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PSProxmoxVE.Core.Authentication;
|
||||
using PSProxmoxVE.Core.Client;
|
||||
using PSProxmoxVE.Core.Models.Nodes;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
|
||||
namespace PSProxmoxVE.Core.Services
|
||||
{
|
||||
@@ -39,6 +41,104 @@ namespace PSProxmoxVE.Core.Services
|
||||
return data?.ToObject<PveNodeStatus>() ?? new PveNodeStatus();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the configuration of a specific node.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
public JObject GetNodeConfig(PveSession session, string node)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"nodes/{node}/config").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data as JObject ?? new JObject();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the configuration of a specific node.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
/// <param name="config">Configuration parameters to update.</param>
|
||||
public void SetNodeConfig(PveSession session, string node, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"nodes/{node}/config", config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the DNS configuration of a specific node.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
public JObject GetNodeDns(PveSession session, string node)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"nodes/{node}/dns").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data as JObject ?? new JObject();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the DNS configuration of a specific node.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
/// <param name="config">DNS configuration parameters to update.</param>
|
||||
public void SetNodeDns(PveSession session, string node, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"nodes/{node}/dns", config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts all VMs and containers on a node. Returns the task UPID.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
/// <param name="config">Optional parameters (e.g. vms to limit which VMs start).</param>
|
||||
public PveTask StartAll(PveSession session, string node, Dictionary<string, string>? config = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
var formData = config ?? new Dictionary<string, string>();
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.PostAsync($"nodes/{node}/startall", formData).GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops all VMs and containers on a node. Returns the task UPID.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
/// <param name="config">Optional parameters (e.g. vms, force-stop).</param>
|
||||
public PveTask StopAll(PveSession session, string node, Dictionary<string, string>? config = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
var formData = config ?? new Dictionary<string, string>();
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.PostAsync($"nodes/{node}/stopall", formData).GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Proxmox VE version running on the server.
|
||||
/// </summary>
|
||||
@@ -54,5 +154,20 @@ namespace PSProxmoxVE.Core.Services
|
||||
throw new InvalidOperationException("Failed to retrieve PVE version from API response.");
|
||||
return PveVersion.Parse(versionStr!);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private static PveTask ParseTask(string response, string node)
|
||||
{
|
||||
var data = JObject.Parse(response)["data"];
|
||||
if (data?.Type == JTokenType.String)
|
||||
return new PveTask { Upid = data.ToString(), Node = node };
|
||||
|
||||
var task = data?.ToObject<PveTask>() ?? new PveTask();
|
||||
task.Node = node;
|
||||
return task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PSProxmoxVE.Core.Authentication;
|
||||
using PSProxmoxVE.Core.Client;
|
||||
using PSProxmoxVE.Core.Models.Cluster;
|
||||
|
||||
namespace PSProxmoxVE.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for Proxmox VE resource pool API operations.
|
||||
/// </summary>
|
||||
public class PoolService
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all resource pools.
|
||||
/// </summary>
|
||||
public PvePool[] GetPools(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync("pools").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PvePool[]>() ?? Array.Empty<PvePool>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a single resource pool by ID.
|
||||
/// </summary>
|
||||
public PvePool? GetPool(PveSession session, string poolId)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"pools/{Uri.EscapeDataString(poolId)}")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PvePool>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new resource pool.
|
||||
/// </summary>
|
||||
public void CreatePool(PveSession session, string poolId, string? comment = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var config = new Dictionary<string, string> { { "poolid", poolId } };
|
||||
if (!string.IsNullOrEmpty(comment))
|
||||
config["comment"] = comment!;
|
||||
|
||||
client.PostAsync("pools", config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing resource pool.
|
||||
/// </summary>
|
||||
public void UpdatePool(PveSession session, string poolId, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"pools/{Uri.EscapeDataString(poolId)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a resource pool.
|
||||
/// </summary>
|
||||
public void RemovePool(PveSession session, string poolId)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"pools/{Uri.EscapeDataString(poolId)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,6 +187,105 @@ namespace PSProxmoxVE.Core.Services
|
||||
client.DeleteAsync($"storage/{storage}").GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a storage definition.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="storage">The storage identifier to update.</param>
|
||||
/// <param name="config">Configuration parameters to update.</param>
|
||||
public void UpdateStorage(PveSession session, string storage, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"storage/{Uri.EscapeDataString(storage)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Storage status & content management
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns the status of a specific storage on a node.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
/// <param name="storage">The storage identifier.</param>
|
||||
public PveStorageStatus GetStorageStatus(PveSession session, string node, string storage)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"nodes/{node}/storage/{storage}/status").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveStorageStatus>() ?? new PveStorageStatus();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a content volume from a storage on a node.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
/// <param name="storage">The storage identifier.</param>
|
||||
/// <param name="volume">The volume identifier to remove.</param>
|
||||
public void RemoveContent(PveSession session, string node, string storage, string volume)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
|
||||
if (string.IsNullOrWhiteSpace(volume)) throw new ArgumentNullException(nameof(volume));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"nodes/{node}/storage/{storage}/content/{Uri.EscapeDataString(volume)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates properties (e.g. notes) of a content volume on a storage.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
/// <param name="storage">The storage identifier.</param>
|
||||
/// <param name="volume">The volume identifier to update.</param>
|
||||
/// <param name="config">Configuration parameters to update.</param>
|
||||
public void UpdateContent(PveSession session, string node, string storage, string volume, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
|
||||
if (string.IsNullOrWhiteSpace(volume)) throw new ArgumentNullException(nameof(volume));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"nodes/{node}/storage/{storage}/content/{Uri.EscapeDataString(volume)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a new disk image on a storage. Returns the task UPID.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
/// <param name="storage">The storage identifier.</param>
|
||||
/// <param name="config">Allocation parameters (filename, size, format).</param>
|
||||
public PveTask AllocateDisk(PveSession session, string node, string storage, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.PostAsync($"nodes/{node}/storage/{storage}/content", config)
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -103,5 +103,57 @@ namespace PSProxmoxVE.Core.Services
|
||||
Thread.Sleep(effectivePoll);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of tasks on the specified node, with optional filters.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The node name.</param>
|
||||
/// <param name="vmid">Optional VM ID filter.</param>
|
||||
/// <param name="source">Optional source filter: "all" or "active".</param>
|
||||
/// <param name="typeFilter">Optional task type filter (e.g., "qmstart").</param>
|
||||
/// <param name="limit">Maximum number of tasks to return. Defaults to 50.</param>
|
||||
public PveTask[] GetTasks(PveSession session, string node, int? vmid = null,
|
||||
string? source = null, string? typeFilter = null, int limit = 50)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var queryParts = new List<string> { $"limit={limit}" };
|
||||
if (vmid.HasValue)
|
||||
queryParts.Add($"vmid={vmid.Value}");
|
||||
if (!string.IsNullOrEmpty(source))
|
||||
queryParts.Add($"source={Uri.EscapeDataString(source!)}");
|
||||
if (!string.IsNullOrEmpty(typeFilter))
|
||||
queryParts.Add($"typefilter={Uri.EscapeDataString(typeFilter!)}");
|
||||
|
||||
var query = string.Join("&", queryParts);
|
||||
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/tasks?{query}")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
var tasks = data?.ToObject<PveTask[]>() ?? Array.Empty<PveTask>();
|
||||
foreach (var t in tasks)
|
||||
t.Node ??= node;
|
||||
return tasks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops (cancels) a running task on the specified node.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The node name.</param>
|
||||
/// <param name="upid">The UPID of the task to stop.</param>
|
||||
public void StopTask(PveSession session, string node, string upid)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(upid)) throw new ArgumentNullException(nameof(upid));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var encodedUpid = Uri.EscapeDataString(upid);
|
||||
client.DeleteAsync($"nodes/{Uri.EscapeDataString(node)}/tasks/{encodedUpid}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,23 @@ namespace PSProxmoxVE.Core.Services
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an API token's configuration.
|
||||
/// </summary>
|
||||
public void UpdateApiToken(PveSession session, string userId, string tokenId, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId));
|
||||
if (string.IsNullOrWhiteSpace(tokenId)) throw new ArgumentNullException(nameof(tokenId));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var encodedUser = Uri.EscapeDataString(userId);
|
||||
var encodedToken = Uri.EscapeDataString(tokenId);
|
||||
client.PutAsync($"access/users/{encodedUser}/token/{encodedToken}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Roles
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -235,6 +252,165 @@ namespace PSProxmoxVE.Core.Services
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a role's privileges.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="roleId">The role identifier to update.</param>
|
||||
/// <param name="privileges">Comma-separated list of privileges.</param>
|
||||
public void UpdateRole(PveSession session, string roleId, string privileges)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(roleId)) throw new ArgumentNullException(nameof(roleId));
|
||||
if (string.IsNullOrWhiteSpace(privileges)) throw new ArgumentNullException(nameof(privileges));
|
||||
|
||||
var formData = new Dictionary<string, string> { ["privs"] = privileges };
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"access/roles/{Uri.EscapeDataString(roleId)}", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Groups
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Returns all groups.</summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
public PveGroup[] GetGroups(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync("access/groups").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveGroup[]>() ?? Array.Empty<PveGroup>();
|
||||
}
|
||||
|
||||
/// <summary>Creates a new group.</summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="groupId">The group identifier.</param>
|
||||
/// <param name="comment">Optional comment/description.</param>
|
||||
public void CreateGroup(PveSession session, string groupId, string? comment = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(groupId)) throw new ArgumentNullException(nameof(groupId));
|
||||
|
||||
var formData = new Dictionary<string, string> { ["groupid"] = groupId };
|
||||
if (!string.IsNullOrEmpty(comment))
|
||||
formData["comment"] = comment!;
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync("access/groups", formData).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>Updates a group's properties.</summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="groupId">The group identifier to update.</param>
|
||||
/// <param name="config">Configuration parameters to update.</param>
|
||||
public void UpdateGroup(PveSession session, string groupId, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(groupId)) throw new ArgumentNullException(nameof(groupId));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"access/groups/{Uri.EscapeDataString(groupId)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>Removes a group.</summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="groupId">The group identifier to remove.</param>
|
||||
public void RemoveGroup(PveSession session, string groupId)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(groupId)) throw new ArgumentNullException(nameof(groupId));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"access/groups/{Uri.EscapeDataString(groupId)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Domains / Realms
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Returns all authentication domains/realms.</summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
public PveDomain[] GetDomains(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync("access/domains").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveDomain[]>() ?? Array.Empty<PveDomain>();
|
||||
}
|
||||
|
||||
/// <summary>Creates a new authentication domain/realm.</summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="config">Domain configuration parameters (must include realm and type).</param>
|
||||
public void CreateDomain(PveSession session, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync("access/domains", config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>Updates an authentication domain/realm.</summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="realm">The realm identifier to update.</param>
|
||||
/// <param name="config">Configuration parameters to update.</param>
|
||||
public void UpdateDomain(PveSession session, string realm, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(realm)) throw new ArgumentNullException(nameof(realm));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"access/domains/{Uri.EscapeDataString(realm)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>Removes an authentication domain/realm.</summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="realm">The realm identifier to remove.</param>
|
||||
public void RemoveDomain(PveSession session, string realm)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(realm)) throw new ArgumentNullException(nameof(realm));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"access/domains/{Uri.EscapeDataString(realm)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Password
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Changes a user's password.</summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="userId">The user ID in "username@realm" format.</param>
|
||||
/// <param name="password">The new password.</param>
|
||||
public void ChangePassword(PveSession session, string userId, string password)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId));
|
||||
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentNullException(nameof(password));
|
||||
|
||||
var formData = new Dictionary<string, string>
|
||||
{
|
||||
["userid"] = userId,
|
||||
["password"] = password
|
||||
};
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync("access/password", formData).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Permissions / ACLs
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -521,6 +521,163 @@ namespace PSProxmoxVE.Core.Services
|
||||
return JObject.Parse(response)["data"] as JObject ?? new JObject();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Disk operations
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Moves a VM disk to a different storage. Returns the task UPID.
|
||||
/// </summary>
|
||||
public PveTask MoveDisk(PveSession session, string node, int vmid, string disk, string storage, string? format = null, bool delete = true)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(disk)) throw new ArgumentNullException(nameof(disk));
|
||||
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
|
||||
|
||||
var formData = new Dictionary<string, string>
|
||||
{
|
||||
["disk"] = disk,
|
||||
["storage"] = storage,
|
||||
["delete"] = delete ? "1" : "0"
|
||||
};
|
||||
if (!string.IsNullOrEmpty(format))
|
||||
formData["format"] = format!;
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.PostAsync($"nodes/{node}/qemu/{vmid}/move_disk", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unlinks (detaches) disks from a VM.
|
||||
/// </summary>
|
||||
public void UnlinkDisk(PveSession session, string node, int vmid, string idlist, bool force = false)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(idlist)) throw new ArgumentNullException(nameof(idlist));
|
||||
|
||||
var formData = new Dictionary<string, string>
|
||||
{
|
||||
["idlist"] = idlist
|
||||
};
|
||||
if (force)
|
||||
formData["force"] = "1";
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"nodes/{node}/qemu/{vmid}/unlink", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Guest agent — extended operations
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves OS information from the QEMU guest agent.
|
||||
/// </summary>
|
||||
public PveGuestOsInfo? GetGuestOsInfo(PveSession session, string node, int vmid)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"nodes/{node}/qemu/{vmid}/agent/get-osinfo")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
var result = data?["result"];
|
||||
return result?.ToObject<PveGuestOsInfo>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves filesystem information from the QEMU guest agent.
|
||||
/// </summary>
|
||||
public PveGuestFsInfo[] GetGuestFsInfo(PveSession session, string node, int vmid)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"nodes/{node}/qemu/{vmid}/agent/get-fsinfo")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
var result = data?["result"];
|
||||
return result?.ToObject<PveGuestFsInfo[]>() ?? Array.Empty<PveGuestFsInfo>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a file from the guest filesystem via the QEMU guest agent.
|
||||
/// </summary>
|
||||
public string ReadGuestFile(PveSession session, string node, int vmid, string file)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(file)) throw new ArgumentNullException(nameof(file));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"nodes/{node}/qemu/{vmid}/agent/file-read?file={Uri.EscapeDataString(file)}")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?["content"]?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes content to a file on the guest filesystem via the QEMU guest agent.
|
||||
/// </summary>
|
||||
public void WriteGuestFile(PveSession session, string node, int vmid, string file, string content)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(file)) throw new ArgumentNullException(nameof(file));
|
||||
|
||||
var formData = new Dictionary<string, string>
|
||||
{
|
||||
["file"] = file,
|
||||
["content"] = content
|
||||
};
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync($"nodes/{node}/qemu/{vmid}/agent/file-write", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a user password inside the guest via the QEMU guest agent.
|
||||
/// </summary>
|
||||
public void SetGuestPassword(PveSession session, string node, int vmid, string username, string password, bool crypted = false)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullException(nameof(username));
|
||||
|
||||
var formData = new Dictionary<string, string>
|
||||
{
|
||||
["username"] = username,
|
||||
["password"] = password
|
||||
};
|
||||
if (crypted)
|
||||
formData["crypted"] = "1";
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync($"nodes/{node}/qemu/{vmid}/agent/set-user-password", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers an fstrim operation inside the guest via the QEMU guest agent.
|
||||
/// </summary>
|
||||
public void GuestFsTrim(PveSession session, string node, int vmid)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync($"nodes/{node}/qemu/{vmid}/agent/fstrim")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// OVA Upload
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Backup
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists guests not covered by any backup job.</para>
|
||||
/// <para type="description">
|
||||
/// Returns information about VMs and containers that are not included in any
|
||||
/// scheduled backup job. Useful for backup compliance auditing.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveBackupInfo")]
|
||||
[OutputType(typeof(PSObject))]
|
||||
public sealed class GetPveBackupInfoCmdlet : PveCmdletBase
|
||||
{
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new BackupService();
|
||||
|
||||
WriteVerbose("Getting guests not covered by backup jobs...");
|
||||
var items = service.GetNotBackedUp(session);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var pso = new PSObject();
|
||||
if (item is Newtonsoft.Json.Linq.JObject jObj)
|
||||
{
|
||||
foreach (var prop in jObj.Properties())
|
||||
{
|
||||
pso.Properties.Add(new PSNoteProperty(prop.Name, prop.Value?.ToObject<object>()));
|
||||
}
|
||||
}
|
||||
WriteObject(pso);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Cluster;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Cluster
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists resources across the Proxmox VE cluster.</para>
|
||||
/// <para type="description">
|
||||
/// Returns cluster-wide resources (VMs, containers, nodes, storage, SDN).
|
||||
/// Optionally filter by resource type or node name.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveClusterResource")]
|
||||
[OutputType(typeof(PveClusterResource))]
|
||||
public sealed class GetPveClusterResourceCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">Filter by resource type.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, Position = 0, HelpMessage = "Filter by resource type (vm, lxc, node, storage, sdn).")]
|
||||
[ValidateSet("vm", "lxc", "node", "storage", "sdn")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Filter results to a specific node name.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Filter results to a specific node name.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new ClusterService();
|
||||
|
||||
WriteVerbose($"Getting cluster resources{(Type != null ? $" of type '{Type}'" : "")}...");
|
||||
var resources = service.GetClusterResources(session, Type);
|
||||
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Node) &&
|
||||
!string.Equals(resource.Node, Node, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
WriteObject(resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Containers;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Gets network interface information for an LXC container.</para>
|
||||
/// <para type="description">
|
||||
/// Retrieves network interface information for the specified LXC container
|
||||
/// via the Proxmox VE API, including interface names, MAC addresses, and IP addresses.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveContainerInterface")]
|
||||
[OutputType(typeof(PveContainerInterface))]
|
||||
public sealed class GetPveContainerInterfaceCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The container identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
|
||||
WriteVerbose($"Getting network interfaces for container {VmId}...");
|
||||
var service = new ContainerService();
|
||||
var interfaces = service.GetInterfaces(session, Node, VmId);
|
||||
|
||||
foreach (var iface in interfaces)
|
||||
WriteObject(iface);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Moves a container volume to a different storage.</para>
|
||||
/// <para type="description">
|
||||
/// Moves the specified volume on an LXC container to a different storage backend
|
||||
/// via the Proxmox VE API. Optionally deletes the original volume after a successful move.
|
||||
/// Use -Wait to block until the move task completes.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Move, "PveContainerVolume", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(PveTask))]
|
||||
public sealed class MovePveContainerVolumeCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The node on which the container resides.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The ID of the container whose volume should be moved.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The volume to move (e.g., "rootfs", "mp0").</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The volume to move (e.g. rootfs, mp0).")]
|
||||
public string Volume { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The target storage to move the volume to.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The target storage identifier.")]
|
||||
public string Storage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When specified, deletes the original volume after a successful move. Default is true.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Delete the original volume after move (default true).")]
|
||||
public SwitchParameter Delete { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When specified, waits for the move task to complete before returning.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")]
|
||||
public SwitchParameter Wait { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"Volume '{Volume}' on container {VmId} (node '{Node}') to storage '{Storage}'", "Move-PveContainerVolume"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var containerService = new ContainerService();
|
||||
|
||||
WriteVerbose($"Moving volume '{Volume}' on container {VmId} to storage '{Storage}'...");
|
||||
var task = containerService.MoveVolume(session, Node, VmId, Volume, Storage, Delete.IsPresent);
|
||||
|
||||
if (Wait.IsPresent)
|
||||
{
|
||||
var taskService = new TaskService();
|
||||
task = taskService.WaitForTask(session, Node, task.Upid, null, null, null);
|
||||
}
|
||||
|
||||
WriteObject(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Converts an LXC container to a template.</para>
|
||||
/// <para type="description">
|
||||
/// Converts the specified LXC container into a template via the Proxmox VE API.
|
||||
/// This operation is irreversible — once converted, the container cannot be started
|
||||
/// and can only be used as a base for cloning new containers.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "PveContainerTemplate",
|
||||
SupportsShouldProcess = true,
|
||||
ConfirmImpact = ConfirmImpact.High)]
|
||||
[OutputType(typeof(PveTask))]
|
||||
public sealed class NewPveContainerTemplateCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The node on which the container resides.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The ID of the container to convert to a template.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"Container {VmId} on node '{Node}'", "New-PveContainerTemplate"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var containerService = new ContainerService();
|
||||
|
||||
WriteVerbose($"Converting container {VmId} to template on node '{Node}'...");
|
||||
var task = containerService.ConvertToTemplate(session, Node, VmId);
|
||||
|
||||
WriteObject(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Resizes a disk attached to an LXC container.</para>
|
||||
/// <para type="description">
|
||||
/// Resizes the specified disk/volume on an LXC container via the Proxmox VE API.
|
||||
/// Use an absolute size (e.g., "50G") to set a fixed size, or a relative size
|
||||
/// prefixed with "+" (e.g., "+5G") to grow the disk by that amount.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Resize, "PveContainerDisk", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(PveTask))]
|
||||
public sealed class ResizePveContainerDiskCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The node on which the container resides.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The ID of the container whose disk should be resized.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The disk/volume to resize (e.g., "rootfs", "mp0").</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The disk/volume to resize (e.g. rootfs, mp0).")]
|
||||
public string Disk { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">
|
||||
/// The new size for the disk. Use an absolute value (e.g., "50G") to set a specific size,
|
||||
/// or a "+" prefix (e.g., "+5G") to grow the disk by the specified amount.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "New disk size (e.g. 50G or +5G to grow).")]
|
||||
public string Size { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"Disk '{Disk}' on container {VmId} (node '{Node}') to size '{Size}'", "Resize-PveContainerDisk"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var containerService = new ContainerService();
|
||||
|
||||
WriteVerbose($"Resizing disk '{Disk}' on container {VmId}...");
|
||||
var task = containerService.ResizeContainerDisk(session, Node, VmId, Disk, Size);
|
||||
|
||||
WriteObject(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Resumes a suspended LXC container on a Proxmox VE node.</para>
|
||||
/// <para type="description">
|
||||
/// Sends a resume command to the specified LXC container via the Proxmox VE API.
|
||||
/// Use -Wait to block until the resume task completes.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsLifecycle.Resume, "PveContainer", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(PveTask))]
|
||||
public sealed class ResumePveContainerCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The node on which the container resides.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The ID of the container to resume.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When specified, waits for the resume task to complete before returning.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")]
|
||||
public SwitchParameter Wait { get; set; }
|
||||
|
||||
/// <summary>Maximum seconds to wait for the status transition when -Wait is specified. Default 60.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Timeout in seconds for -Wait (default 60).")]
|
||||
[ValidateRange(1, 3600)]
|
||||
public int Timeout { get; set; } = 60;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"Container {VmId} on node '{Node}'", "Resume-PveContainer"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var containerService = new ContainerService();
|
||||
|
||||
WriteVerbose($"Resuming container {VmId} on node '{Node}'...");
|
||||
var task = containerService.ResumeContainer(session, Node, VmId);
|
||||
|
||||
if (Wait.IsPresent)
|
||||
{
|
||||
var taskService = new TaskService();
|
||||
task = taskService.WaitForTask(session, Node, task.Upid, null, null, null);
|
||||
}
|
||||
|
||||
WriteObject(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Suspends an LXC container on a Proxmox VE node.</para>
|
||||
/// <para type="description">
|
||||
/// Sends a suspend command to the specified LXC container via the Proxmox VE API.
|
||||
/// Use -Wait to block until the suspend task completes.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsLifecycle.Suspend, "PveContainer", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(PveTask))]
|
||||
public sealed class SuspendPveContainerCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The node on which the container resides.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The ID of the container to suspend.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When specified, waits for the suspend task to complete before returning.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")]
|
||||
public SwitchParameter Wait { get; set; }
|
||||
|
||||
/// <summary>Maximum seconds to wait for the status transition when -Wait is specified. Default 60.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Timeout in seconds for -Wait (default 60).")]
|
||||
[ValidateRange(1, 3600)]
|
||||
public int Timeout { get; set; } = 60;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"Container {VmId} on node '{Node}'", "Suspend-PveContainer"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var containerService = new ContainerService();
|
||||
|
||||
WriteVerbose($"Suspending container {VmId} on node '{Node}'...");
|
||||
var task = containerService.SuspendContainer(session, Node, VmId);
|
||||
|
||||
if (Wait.IsPresent)
|
||||
{
|
||||
var taskService = new TaskService();
|
||||
task = taskService.WaitForTask(session, Node, task.Upid, null, null, null);
|
||||
}
|
||||
|
||||
WriteObject(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Applies pending SDN configuration changes in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Applies all pending Software-Defined Networking configuration changes cluster-wide.
|
||||
/// Run this after making changes with Set-PveSdnZone, Set-PveSdnVnet, etc.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsLifecycle.Invoke, "PveSdnApply", SupportsShouldProcess = true)]
|
||||
public class InvokePveSdnApplyCmdlet : PveCmdletBase
|
||||
{
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess("SDN configuration", "Apply pending changes"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
RequireVersion(session, "SDN", 6, 2, 8, 0);
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose("Applying pending SDN configuration changes...");
|
||||
service.ApplySdn(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates an SDN controller configuration in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies the specified Software-Defined Networking controller configuration.
|
||||
/// Changes are pending until Invoke-PveSdnApply is called.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveSdnController", SupportsShouldProcess = true)]
|
||||
public class SetPveSdnControllerCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The controller identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN controller identifier.")]
|
||||
public string Controller { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The Autonomous System Number for BGP/EVPN.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The Autonomous System Number for BGP/EVPN.")]
|
||||
public int? Asn { get; set; }
|
||||
|
||||
/// <summary>Comma-separated list of BGP peer addresses.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of BGP peer addresses.")]
|
||||
public string? Peers { get; set; }
|
||||
|
||||
/// <summary>The node this controller is configured on.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node this controller is configured on.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Controller, "Set PVE SDN Controller"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
RequireVersion(session, "SDN", 6, 2, 8, 0);
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Updating SDN controller '{Controller}'...");
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (MyInvocation.BoundParameters.ContainsKey("Asn"))
|
||||
config["asn"] = Asn!.Value.ToString();
|
||||
if (!string.IsNullOrEmpty(Peers)) config["peers"] = Peers!;
|
||||
if (!string.IsNullOrEmpty(Node)) config["node"] = Node!;
|
||||
|
||||
service.UpdateSdnController(session, Controller, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates an SDN DNS plugin configuration in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies the specified Software-Defined Networking DNS plugin configuration.
|
||||
/// Changes are pending until Invoke-PveSdnApply is called.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveSdnDns", SupportsShouldProcess = true)]
|
||||
public class SetPveSdnDnsCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The DNS plugin identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN DNS plugin identifier.")]
|
||||
public string Dns { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The DNS server URL.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The DNS server URL.")]
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>The TSIG key for DNS updates.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The TSIG key for DNS updates.")]
|
||||
public string? Key { get; set; }
|
||||
|
||||
/// <summary>The IPv6 reverse DNS mask length.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The IPv6 reverse DNS mask length.")]
|
||||
public int? ReverseMaskV6 { get; set; }
|
||||
|
||||
/// <summary>The TTL for DNS records.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The TTL for DNS records.")]
|
||||
public int? Ttl { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Dns, "Set PVE SDN DNS"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
RequireVersion(session, "SDN", 6, 2, 8, 0);
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Updating SDN DNS plugin '{Dns}'...");
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Url)) config["url"] = Url!;
|
||||
if (!string.IsNullOrEmpty(Key)) config["key"] = Key!;
|
||||
if (MyInvocation.BoundParameters.ContainsKey("ReverseMaskV6"))
|
||||
config["reversemaskv6"] = ReverseMaskV6!.Value.ToString();
|
||||
if (MyInvocation.BoundParameters.ContainsKey("Ttl"))
|
||||
config["ttl"] = Ttl!.Value.ToString();
|
||||
|
||||
service.UpdateSdnDns(session, Dns, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates an SDN IPAM plugin configuration in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies the specified Software-Defined Networking IPAM plugin configuration.
|
||||
/// Changes are pending until Invoke-PveSdnApply is called.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveSdnIpam", SupportsShouldProcess = true)]
|
||||
public class SetPveSdnIpamCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The IPAM plugin identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN IPAM plugin identifier.")]
|
||||
public string Ipam { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The IPAM server URL.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The IPAM server URL.")]
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>The API token for the IPAM server.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The API token for the IPAM server.")]
|
||||
public string? Token { get; set; }
|
||||
|
||||
/// <summary>The section ID in the IPAM server.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The section ID in the IPAM server.")]
|
||||
public int? Section { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Ipam, "Set PVE SDN IPAM"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
RequireVersion(session, "SDN", 6, 2, 8, 0);
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Updating SDN IPAM plugin '{Ipam}'...");
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Url)) config["url"] = Url!;
|
||||
if (!string.IsNullOrEmpty(Token)) config["token"] = Token!;
|
||||
if (MyInvocation.BoundParameters.ContainsKey("Section"))
|
||||
config["section"] = Section!.Value.ToString();
|
||||
|
||||
service.UpdateSdnIpam(session, Ipam, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates an SDN subnet configuration in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies the specified Software-Defined Networking subnet configuration on a VNet.
|
||||
/// Changes are pending until Invoke-PveSdnApply is called.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveSdnSubnet", SupportsShouldProcess = true)]
|
||||
public class SetPveSdnSubnetCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The VNet this subnet belongs to.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The SDN VNet name.")]
|
||||
public string Vnet { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The subnet CIDR.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The subnet CIDR.")]
|
||||
public string Subnet { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gateway address for this subnet.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Gateway address for this subnet.")]
|
||||
public string? Gateway { get; set; }
|
||||
|
||||
/// <summary>Enable SNAT for this subnet.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Enable SNAT for this subnet.")]
|
||||
public SwitchParameter Snat { get; set; }
|
||||
|
||||
/// <summary>DNS zone prefix for automatic registration.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "DNS zone prefix for automatic registration.")]
|
||||
public string? DnsZonePrefix { get; set; }
|
||||
|
||||
/// <summary>DHCP range for this subnet.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "DHCP range for this subnet.")]
|
||||
public string? DhcpRange { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Subnet, "Set PVE SDN Subnet"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
RequireVersion(session, "SDN", 6, 2, 8, 0);
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Updating SDN subnet '{Subnet}' on VNet '{Vnet}'...");
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Gateway)) config["gateway"] = Gateway!;
|
||||
if (MyInvocation.BoundParameters.ContainsKey("Snat"))
|
||||
config["snat"] = Snat.IsPresent ? "1" : "0";
|
||||
if (!string.IsNullOrEmpty(DnsZonePrefix)) config["dnszoneprefix"] = DnsZonePrefix!;
|
||||
if (!string.IsNullOrEmpty(DhcpRange)) config["dhcp-range"] = DhcpRange!;
|
||||
|
||||
service.UpdateSdnSubnet(session, Vnet, Subnet, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates an SDN VNet configuration in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies the specified Software-Defined Networking VNet configuration.
|
||||
/// Changes are pending until Invoke-PveSdnApply is called.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveSdnVnet", SupportsShouldProcess = true)]
|
||||
public class SetPveSdnVnetCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The VNet identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN VNet name.")]
|
||||
public string Vnet { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>An alias for the VNet.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "An alias for the VNet.")]
|
||||
public string? Alias { get; set; }
|
||||
|
||||
/// <summary>The zone this VNet belongs to.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The zone this VNet belongs to.")]
|
||||
public string? Zone { get; set; }
|
||||
|
||||
/// <summary>VLAN tag for this VNet.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "VLAN tag for this VNet.")]
|
||||
public int? Tag { get; set; }
|
||||
|
||||
/// <summary>Enable VLAN-aware bridging.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Enable VLAN-aware bridging.")]
|
||||
public SwitchParameter VlanAware { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Vnet, "Set PVE SDN VNet"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
RequireVersion(session, "SDN", 6, 2, 8, 0);
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Updating SDN VNet '{Vnet}'...");
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Alias)) config["alias"] = Alias!;
|
||||
if (!string.IsNullOrEmpty(Zone)) config["zone"] = Zone!;
|
||||
if (MyInvocation.BoundParameters.ContainsKey("Tag"))
|
||||
config["tag"] = Tag!.Value.ToString();
|
||||
if (MyInvocation.BoundParameters.ContainsKey("VlanAware"))
|
||||
config["vlanaware"] = VlanAware.IsPresent ? "1" : "0";
|
||||
|
||||
service.UpdateSdnVnet(session, Vnet, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates an SDN zone configuration in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies the specified Software-Defined Networking zone configuration.
|
||||
/// Changes are pending until Invoke-PveSdnApply is called.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveSdnZone", SupportsShouldProcess = true)]
|
||||
public class SetPveSdnZoneCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The zone identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN zone name.")]
|
||||
public string Zone { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>DNS server for automatic DNS registration.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "DNS server for automatic registration.")]
|
||||
public string? Dns { get; set; }
|
||||
|
||||
/// <summary>Reverse DNS server.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Reverse DNS server.")]
|
||||
public string? Reversedns { get; set; }
|
||||
|
||||
/// <summary>DNS zone name for registration.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "DNS zone name for registration.")]
|
||||
public string? DnsZone { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Zone, "Set PVE SDN Zone"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
RequireVersion(session, "SDN", 6, 2, 8, 0);
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Updating SDN zone '{Zone}'...");
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Dns)) config["dns"] = Dns!;
|
||||
if (!string.IsNullOrEmpty(Reversedns)) config["reversedns"] = Reversedns!;
|
||||
if (!string.IsNullOrEmpty(DnsZone)) config["dnszone"] = DnsZone!;
|
||||
|
||||
service.UpdateSdnZone(session, Zone, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Returns the configuration of a Proxmox VE node.</para>
|
||||
/// <para type="description">
|
||||
/// Retrieves the node configuration from the /nodes/{node}/config endpoint,
|
||||
/// including description, wakeonlan settings and other node-level properties.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveNodeConfig")]
|
||||
[OutputType(typeof(PSObject))]
|
||||
public class GetPveNodeConfigCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new NodeService();
|
||||
|
||||
WriteVerbose($"Getting configuration for node '{Node}'...");
|
||||
var config = service.GetNodeConfig(session, Node);
|
||||
|
||||
var psObj = new PSObject();
|
||||
foreach (var prop in config.Properties())
|
||||
{
|
||||
psObj.Properties.Add(new PSNoteProperty(prop.Name, prop.Value.ToString()));
|
||||
}
|
||||
|
||||
WriteObject(psObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Returns the DNS configuration of a Proxmox VE node.</para>
|
||||
/// <para type="description">
|
||||
/// Retrieves the DNS settings (search domain and nameservers) for the specified
|
||||
/// node from the /nodes/{node}/dns endpoint.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveNodeDns")]
|
||||
[OutputType(typeof(PSObject))]
|
||||
public class GetPveNodeDnsCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new NodeService();
|
||||
|
||||
WriteVerbose($"Getting DNS configuration for node '{Node}'...");
|
||||
var dns = service.GetNodeDns(session, Node);
|
||||
|
||||
var psObj = new PSObject();
|
||||
foreach (var prop in dns.Properties())
|
||||
{
|
||||
psObj.Properties.Add(new PSNoteProperty(prop.Name, prop.Value.ToString()));
|
||||
}
|
||||
|
||||
WriteObject(psObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates the configuration of a Proxmox VE node.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies node-level configuration properties such as description and wake-on-LAN
|
||||
/// settings via the /nodes/{node}/config endpoint.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveNodeConfig", SupportsShouldProcess = true)]
|
||||
public class SetPveNodeConfigCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Node description/comment.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Node description or comment.")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>Wake-on-LAN MAC address or configuration.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Wake-on-LAN configuration.")]
|
||||
public string? Wakeonlan { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Node, "Set PVE Node Config"))
|
||||
return;
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
if (Description != null) config["description"] = Description;
|
||||
if (!string.IsNullOrEmpty(Wakeonlan)) config["wakeonlan"] = Wakeonlan!;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new NodeService();
|
||||
|
||||
WriteVerbose($"Updating configuration for node '{Node}'...");
|
||||
service.SetNodeConfig(session, Node, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates the DNS configuration of a Proxmox VE node.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies the DNS settings (search domain and nameservers) for the specified
|
||||
/// node via the /nodes/{node}/dns endpoint.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveNodeDns", SupportsShouldProcess = true)]
|
||||
public class SetPveNodeDnsCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>DNS search domain.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The DNS search domain.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Search { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Primary DNS server.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Primary DNS server address.")]
|
||||
public string? Dns1 { get; set; }
|
||||
|
||||
/// <summary>Secondary DNS server.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Secondary DNS server address.")]
|
||||
public string? Dns2 { get; set; }
|
||||
|
||||
/// <summary>Tertiary DNS server.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Tertiary DNS server address.")]
|
||||
public string? Dns3 { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Node, "Set PVE Node DNS"))
|
||||
return;
|
||||
|
||||
var config = new Dictionary<string, string>
|
||||
{
|
||||
["search"] = Search
|
||||
};
|
||||
if (!string.IsNullOrEmpty(Dns1)) config["dns1"] = Dns1!;
|
||||
if (!string.IsNullOrEmpty(Dns2)) config["dns2"] = Dns2!;
|
||||
if (!string.IsNullOrEmpty(Dns3)) config["dns3"] = Dns3!;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new NodeService();
|
||||
|
||||
WriteVerbose($"Updating DNS configuration for node '{Node}'...");
|
||||
service.SetNodeDns(session, Node, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Starts all VMs and containers on a Proxmox VE node.</para>
|
||||
/// <para type="description">
|
||||
/// Triggers the start-all operation on the specified node, which starts all VMs and
|
||||
/// containers that are configured to auto-start. Optionally limit to specific VM IDs.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsLifecycle.Start, "PveNodeVms", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(PveTask))]
|
||||
public class StartPveNodeVmsCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Comma-separated list of VM IDs to start. If omitted, all VMs are started.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of VM IDs to start.")]
|
||||
public string? VmIds { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Node, "Start all VMs"))
|
||||
return;
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
if (!string.IsNullOrEmpty(VmIds)) config["vms"] = VmIds!;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new NodeService();
|
||||
|
||||
WriteVerbose($"Starting all VMs on node '{Node}'...");
|
||||
var task = service.StartAll(session, Node, config);
|
||||
|
||||
WriteObject(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Stops all VMs and containers on a Proxmox VE node.</para>
|
||||
/// <para type="description">
|
||||
/// Triggers the stop-all operation on the specified node, which stops all running VMs
|
||||
/// and containers. Optionally limit to specific VM IDs or force-stop.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsLifecycle.Stop, "PveNodeVms",
|
||||
SupportsShouldProcess = true,
|
||||
ConfirmImpact = ConfirmImpact.High)]
|
||||
[OutputType(typeof(PveTask))]
|
||||
public class StopPveNodeVmsCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Comma-separated list of VM IDs to stop. If omitted, all VMs are stopped.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of VM IDs to stop.")]
|
||||
public string? VmIds { get; set; }
|
||||
|
||||
/// <summary>Force stop VMs (do not wait for graceful shutdown).</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Force stop VMs without waiting for graceful shutdown.")]
|
||||
public SwitchParameter ForceStop { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Node, "Stop all VMs"))
|
||||
return;
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
if (!string.IsNullOrEmpty(VmIds)) config["vms"] = VmIds!;
|
||||
if (ForceStop.IsPresent) config["force-stop"] = "1";
|
||||
|
||||
var session = GetSession();
|
||||
var service = new NodeService();
|
||||
|
||||
WriteVerbose($"Stopping all VMs on node '{Node}'...");
|
||||
var task = service.StopAll(session, Node, config);
|
||||
|
||||
WriteObject(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Cluster;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Pools
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists Proxmox VE resource pools.</para>
|
||||
/// <para type="description">
|
||||
/// Returns resource pool configurations from the Proxmox VE cluster.
|
||||
/// Optionally filter by pool ID to retrieve a specific pool with its members.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PvePool")]
|
||||
[OutputType(typeof(PvePool))]
|
||||
public sealed class GetPvePoolCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The pool ID to retrieve. When omitted, all pools are returned.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The pool ID to retrieve.")]
|
||||
public string? PoolId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new PoolService();
|
||||
|
||||
if (!string.IsNullOrEmpty(PoolId))
|
||||
{
|
||||
WriteVerbose($"Getting pool '{PoolId}'...");
|
||||
var pool = service.GetPool(session, PoolId!);
|
||||
WriteObject(pool);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteVerbose("Getting all pools...");
|
||||
var pools = service.GetPools(session);
|
||||
foreach (var pool in pools)
|
||||
{
|
||||
WriteObject(pool);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Pools
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Creates a new resource pool in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Creates a new resource pool in the Proxmox VE cluster with the specified ID
|
||||
/// and optional comment.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "PvePool", SupportsShouldProcess = true)]
|
||||
public sealed class NewPvePoolCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The pool ID to create.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The pool ID to create.")]
|
||||
public string PoolId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">An optional comment or description for the pool.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "An optional comment or description for the pool.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"pool '{PoolId}'", "New-PvePool"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new PoolService();
|
||||
|
||||
WriteVerbose($"Creating pool '{PoolId}'...");
|
||||
service.CreatePool(session, PoolId, Comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Pools
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Removes a resource pool from Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Deletes a resource pool from the Proxmox VE cluster configuration.
|
||||
/// This operation is destructive and requires confirmation unless -Force is specified.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "PvePool",
|
||||
SupportsShouldProcess = true,
|
||||
ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemovePvePoolCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The pool ID to remove.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The pool ID to remove.")]
|
||||
public string PoolId { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"pool '{PoolId}'", "Remove-PvePool"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new PoolService();
|
||||
|
||||
WriteVerbose($"Removing pool '{PoolId}'...");
|
||||
service.RemovePool(session, PoolId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Pools
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates a resource pool in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies an existing resource pool configuration. You can update the comment,
|
||||
/// add members, or remove members.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PvePool", SupportsShouldProcess = true)]
|
||||
public sealed class SetPvePoolCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The pool ID to update.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The pool ID to update.")]
|
||||
public string PoolId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">An optional comment or description for the pool.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "An optional comment or description for the pool.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Comma-separated list of members to add (e.g., "100,200,storage1").</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of members to add (e.g., '100,200,storage1').")]
|
||||
public string? Members { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Comma-separated list of members to remove.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of members to remove.")]
|
||||
public string? Delete { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"pool '{PoolId}'", "Set-PvePool"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new PoolService();
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
if (Comment != null)
|
||||
config["comment"] = Comment;
|
||||
if (!string.IsNullOrEmpty(Members))
|
||||
config["members"] = Members!;
|
||||
if (!string.IsNullOrEmpty(Delete))
|
||||
config["delete"] = Delete!;
|
||||
|
||||
WriteVerbose($"Updating pool '{PoolId}'...");
|
||||
service.UpdatePool(session, PoolId, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Storage;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Returns the status of a Proxmox VE storage on a node.</para>
|
||||
/// <para type="description">
|
||||
/// Retrieves capacity, usage and activation status for the specified storage
|
||||
/// on the given node from the /nodes/{node}/storage/{storage}/status endpoint.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveStorageStatus")]
|
||||
[OutputType(typeof(PveStorageStatus))]
|
||||
public class GetPveStorageStatusCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The storage identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The storage pool name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Storage { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new StorageService();
|
||||
|
||||
WriteVerbose($"Getting status for storage '{Storage}' on node '{Node}'...");
|
||||
var status = service.GetStorageStatus(session, Node, Storage);
|
||||
status.Storage = Storage;
|
||||
status.Node = Node;
|
||||
|
||||
WriteObject(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Allocates a new disk image on a Proxmox VE storage.</para>
|
||||
/// <para type="description">
|
||||
/// Creates a new disk image with the specified filename, size and optional format
|
||||
/// on the given storage and node. Returns the volume ID of the newly created disk.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "PveStorageDisk", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(string))]
|
||||
public class NewPveStorageDiskCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The storage identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The storage pool name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Storage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The filename for the new disk (e.g. "vm-100-disk-1").</summary>
|
||||
[Parameter(Mandatory = true, Position = 2, HelpMessage = "The filename for the new disk (e.g. vm-100-disk-1).")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Filename { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The size of the disk (e.g. "32G", "100M").</summary>
|
||||
[Parameter(Mandatory = true, Position = 3, HelpMessage = "The size of the disk (e.g. 32G, 100M).")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Size { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The disk image format.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The disk image format (raw, qcow2, vmdk).")]
|
||||
[ValidateSet("raw", "qcow2", "vmdk", IgnoreCase = true)]
|
||||
public string? Format { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"{Filename} ({Size})", "New PVE Storage Disk"))
|
||||
return;
|
||||
|
||||
var config = new Dictionary<string, string>
|
||||
{
|
||||
["filename"] = Filename,
|
||||
["size"] = Size
|
||||
};
|
||||
if (!string.IsNullOrEmpty(Format))
|
||||
config["format"] = Format!;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new StorageService();
|
||||
|
||||
WriteVerbose($"Allocating disk '{Filename}' ({Size}) on storage '{Storage}' on node '{Node}'...");
|
||||
var task = service.AllocateDisk(session, Node, Storage, config);
|
||||
|
||||
WriteObject(task.Upid ?? string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Removes a content volume from a Proxmox VE storage.</para>
|
||||
/// <para type="description">
|
||||
/// Deletes the specified volume (disk image, ISO, template, backup, etc.) from the
|
||||
/// storage on the given node.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "PveStorageContent",
|
||||
SupportsShouldProcess = true,
|
||||
ConfirmImpact = ConfirmImpact.High)]
|
||||
public class RemovePveStorageContentCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The storage identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The storage pool name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Storage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The volume identifier to remove (e.g. "local:iso/ubuntu.iso").</summary>
|
||||
[Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true, HelpMessage = "The volume identifier to remove.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Volume { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Volume, "Remove PVE Storage Content"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new StorageService();
|
||||
|
||||
WriteVerbose($"Removing volume '{Volume}' from storage '{Storage}' on node '{Node}'...");
|
||||
service.RemoveContent(session, Node, Storage, Volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates a Proxmox VE storage definition.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies the configuration of an existing storage definition.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveStorage", SupportsShouldProcess = true)]
|
||||
public sealed class SetPveStorageCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The storage identifier to update.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The storage identifier.")]
|
||||
public string Storage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Comma-separated list of content types (e.g. 'images,iso,backup').</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comma-separated content types (e.g. 'images,iso,backup').")]
|
||||
public string? Content { get; set; }
|
||||
|
||||
/// <summary>Comma-separated list of nodes that can access this storage.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of nodes.")]
|
||||
public string? Nodes { get; set; }
|
||||
|
||||
/// <summary>Whether the storage is enabled.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Whether the storage is enabled.")]
|
||||
public SwitchParameter Enabled { get; set; }
|
||||
|
||||
/// <summary>Whether the storage is shared across nodes.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Whether the storage is shared.")]
|
||||
public SwitchParameter Shared { get; set; }
|
||||
|
||||
/// <summary>Additional configuration as a hashtable for parameters not covered by named parameters.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Additional configuration parameters as a hashtable.")]
|
||||
public System.Collections.Hashtable? AdditionalConfig { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"storage '{Storage}'", "Set"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new StorageService();
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
if (!string.IsNullOrEmpty(Content)) config["content"] = Content!;
|
||||
if (!string.IsNullOrEmpty(Nodes)) config["nodes"] = Nodes!;
|
||||
if (MyInvocation.BoundParameters.ContainsKey("Enabled"))
|
||||
config["disable"] = Enabled.IsPresent ? "0" : "1";
|
||||
if (MyInvocation.BoundParameters.ContainsKey("Shared"))
|
||||
config["shared"] = Shared.IsPresent ? "1" : "0";
|
||||
|
||||
if (AdditionalConfig != null)
|
||||
{
|
||||
foreach (var key in AdditionalConfig.Keys)
|
||||
config[key.ToString()!] = AdditionalConfig[key]?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
WriteVerbose($"Updating storage '{Storage}'...");
|
||||
service.UpdateStorage(session, Storage, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates properties of a content volume in Proxmox VE storage.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies metadata (such as notes/description) of an existing volume in the specified
|
||||
/// storage on the given node.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveStorageContent", SupportsShouldProcess = true)]
|
||||
public class SetPveStorageContentCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The storage identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The storage pool name.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Storage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The volume identifier to update.</summary>
|
||||
[Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true, HelpMessage = "The volume identifier to update.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Volume { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Notes/description to set on the volume.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Notes or description for the volume.")]
|
||||
public string? Notes { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Volume, "Set PVE Storage Content"))
|
||||
return;
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
if (Notes != null) config["notes"] = Notes;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new StorageService();
|
||||
|
||||
WriteVerbose($"Updating volume '{Volume}' on storage '{Storage}' on node '{Node}'...");
|
||||
service.UpdateContent(session, Node, Storage, Volume, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists tasks on a Proxmox VE node.</para>
|
||||
/// <para type="description">
|
||||
/// Returns a list of recent tasks on the specified node. Use optional parameters
|
||||
/// to filter by VM ID, source, or task type. Unlike Get-PveTask which retrieves
|
||||
/// a single task by UPID, this cmdlet lists multiple tasks.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveTaskList")]
|
||||
[OutputType(typeof(PveTask))]
|
||||
public sealed class GetPveTaskListCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The node to list tasks from.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Filter tasks by VM ID.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Filter tasks by VM ID.")]
|
||||
public int? VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Filter by task source: all or active.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Filter by task source: all or active.")]
|
||||
[ValidateSet("all", "active")]
|
||||
public string? Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Filter by task type (e.g., qmstart, vzdump).</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Filter by task type (e.g., qmstart, vzdump).")]
|
||||
public string? TypeFilter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Maximum number of tasks to return. Defaults to 50.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Maximum number of tasks to return. Defaults to 50.")]
|
||||
[ValidateRange(1, 10000)]
|
||||
public int Limit { get; set; } = 50;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new TaskService();
|
||||
|
||||
WriteVerbose($"Getting tasks on node '{Node}' (limit={Limit})...");
|
||||
var tasks = service.GetTasks(session, Node, VmId, Source, TypeFilter, Limit);
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
WriteObject(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Stops a running task on a Proxmox VE node.</para>
|
||||
/// <para type="description">
|
||||
/// Cancels a running task identified by its UPID on the specified node.
|
||||
/// This is a destructive operation and requires confirmation.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsLifecycle.Stop, "PveTask",
|
||||
SupportsShouldProcess = true,
|
||||
ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class StopPveTaskCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The node on which the task is running.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The UPID of the task to stop.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The task UPID.")]
|
||||
public string Upid { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"task '{Upid}' on node '{Node}'", "Stop-PveTask"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new TaskService();
|
||||
|
||||
WriteVerbose($"Stopping task '{Upid}' on node '{Node}'...");
|
||||
service.StopTask(session, Node, Upid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Users;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists Proxmox VE authentication domains/realms.</para>
|
||||
/// <para type="description">
|
||||
/// Returns all authentication domains (realms) from the Proxmox VE access management
|
||||
/// system. Optionally filter by a specific realm name.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveDomain")]
|
||||
[OutputType(typeof(PveDomain))]
|
||||
public class GetPveDomainCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>Optional realm name to filter by.</summary>
|
||||
[Parameter(Mandatory = false, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "Optional realm name to filter results.")]
|
||||
public string? Realm { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new UserService();
|
||||
|
||||
WriteVerbose("Getting authentication domains...");
|
||||
var domains = service.GetDomains(session);
|
||||
|
||||
if (!string.IsNullOrEmpty(Realm))
|
||||
{
|
||||
domains = domains.Where(d => d.Realm == Realm).ToArray();
|
||||
}
|
||||
|
||||
foreach (var domain in domains)
|
||||
{
|
||||
WriteObject(domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Users;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists Proxmox VE groups.</para>
|
||||
/// <para type="description">
|
||||
/// Returns all groups from the Proxmox VE access management system. Optionally
|
||||
/// filter by a specific group ID.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveGroup")]
|
||||
[OutputType(typeof(PveGroup))]
|
||||
public class GetPveGroupCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>Optional group ID to filter by.</summary>
|
||||
[Parameter(Mandatory = false, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "Optional group ID to filter results.")]
|
||||
public string? GroupId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new UserService();
|
||||
|
||||
WriteVerbose("Getting groups...");
|
||||
var groups = service.GetGroups(session);
|
||||
|
||||
if (!string.IsNullOrEmpty(GroupId))
|
||||
{
|
||||
groups = groups.Where(g => g.GroupId == GroupId).ToArray();
|
||||
}
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
WriteObject(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Users;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Creates a new Proxmox VE authentication domain/realm.</para>
|
||||
/// <para type="description">
|
||||
/// Adds a new authentication domain (realm) to the Proxmox VE access management system.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "PveDomain", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(PveDomain))]
|
||||
public class NewPveDomainCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The realm identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The realm identifier.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Realm { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The domain type.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The domain type (pam, pve, ad, ldap, openid).")]
|
||||
[ValidateSet("pam", "pve", "ad", "ldap", "openid", IgnoreCase = true)]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Optional comment/description for the domain.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comment or description for the domain.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>Set this realm as the default authentication domain.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Set as the default authentication domain.")]
|
||||
public SwitchParameter Default { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Realm, "Create PVE Domain"))
|
||||
return;
|
||||
|
||||
var config = new Dictionary<string, string>
|
||||
{
|
||||
["realm"] = Realm,
|
||||
["type"] = Type
|
||||
};
|
||||
if (!string.IsNullOrEmpty(Comment)) config["comment"] = Comment!;
|
||||
if (Default.IsPresent) config["default"] = "1";
|
||||
|
||||
var session = GetSession();
|
||||
var service = new UserService();
|
||||
|
||||
WriteVerbose($"Creating domain '{Realm}' of type '{Type}'...");
|
||||
service.CreateDomain(session, config);
|
||||
|
||||
WriteObject(new PveDomain
|
||||
{
|
||||
Realm = Realm,
|
||||
Type = Type,
|
||||
Comment = Comment,
|
||||
Default = Default.IsPresent ? 1 : 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Users;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Creates a new Proxmox VE group.</para>
|
||||
/// <para type="description">
|
||||
/// Adds a new group to the Proxmox VE access management system.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "PveGroup", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(PveGroup))]
|
||||
public class NewPveGroupCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The group identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The group identifier.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Optional comment/description for the group.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comment or description for the group.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(GroupId, "Create PVE Group"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new UserService();
|
||||
|
||||
WriteVerbose($"Creating group '{GroupId}'...");
|
||||
service.CreateGroup(session, GroupId, Comment);
|
||||
|
||||
WriteObject(new PveGroup
|
||||
{
|
||||
GroupId = GroupId,
|
||||
Comment = Comment
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Removes a Proxmox VE authentication domain/realm.</para>
|
||||
/// <para type="description">
|
||||
/// Deletes the specified authentication domain (realm) from the Proxmox VE
|
||||
/// access management system.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "PveDomain",
|
||||
SupportsShouldProcess = true,
|
||||
ConfirmImpact = ConfirmImpact.High)]
|
||||
public class RemovePveDomainCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The realm identifier to remove.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The realm identifier to remove.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Realm { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Realm, "Remove PVE Domain"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new UserService();
|
||||
|
||||
WriteVerbose($"Removing domain '{Realm}'...");
|
||||
service.RemoveDomain(session, Realm);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Removes a Proxmox VE group.</para>
|
||||
/// <para type="description">
|
||||
/// Deletes the specified group from the Proxmox VE access management system.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "PveGroup",
|
||||
SupportsShouldProcess = true,
|
||||
ConfirmImpact = ConfirmImpact.High)]
|
||||
public class RemovePveGroupCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The group identifier to remove.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The group identifier to remove.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(GroupId, "Remove PVE Group"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new UserService();
|
||||
|
||||
WriteVerbose($"Removing group '{GroupId}'...");
|
||||
service.RemoveGroup(session, GroupId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates a Proxmox VE API token configuration.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies properties of an existing API token such as its comment,
|
||||
/// expiration, or privilege separation setting.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveApiToken", SupportsShouldProcess = true)]
|
||||
public sealed class SetPveApiTokenCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The user ID that owns the token (e.g. 'user@realm').</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The user ID (e.g. 'user@realm').")]
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The token identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The token identifier.")]
|
||||
public string TokenId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Optional comment for the token.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comment describing the token.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>Token expiration as a Unix epoch timestamp. Set to 0 for no expiration.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Expiration as Unix epoch (0 = no expiration).")]
|
||||
public long? Expire { get; set; }
|
||||
|
||||
/// <summary>Whether the token uses privilege separation.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Whether the token uses privilege separation.")]
|
||||
public SwitchParameter PrivilegeSeparation { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"API token '{UserId}!{TokenId}'", "Set"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new UserService();
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
if (!string.IsNullOrEmpty(Comment)) config["comment"] = Comment!;
|
||||
if (Expire.HasValue) config["expire"] = Expire.Value.ToString();
|
||||
if (MyInvocation.BoundParameters.ContainsKey("PrivilegeSeparation"))
|
||||
config["privsep"] = PrivilegeSeparation.IsPresent ? "1" : "0";
|
||||
|
||||
WriteVerbose($"Updating API token '{UserId}!{TokenId}'...");
|
||||
service.UpdateApiToken(session, UserId, TokenId, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates a Proxmox VE authentication domain/realm.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies properties of an existing authentication domain (realm) in the
|
||||
/// Proxmox VE access management system.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveDomain", SupportsShouldProcess = true)]
|
||||
public class SetPveDomainCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The realm identifier to update.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The realm identifier.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string Realm { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Updated comment/description for the domain.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Updated comment or description for the domain.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>Set this realm as the default authentication domain.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Set as the default authentication domain.")]
|
||||
public SwitchParameter Default { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(Realm, "Set PVE Domain"))
|
||||
return;
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
if (Comment != null) config["comment"] = Comment;
|
||||
if (Default.IsPresent) config["default"] = "1";
|
||||
|
||||
var session = GetSession();
|
||||
var service = new UserService();
|
||||
|
||||
WriteVerbose($"Updating domain '{Realm}'...");
|
||||
service.UpdateDomain(session, Realm, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates a Proxmox VE group.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies properties of an existing group in the Proxmox VE access management system.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveGroup", SupportsShouldProcess = true)]
|
||||
public class SetPveGroupCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The group identifier to update.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The group identifier.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Updated comment/description for the group.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Updated comment or description for the group.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(GroupId, "Set PVE Group"))
|
||||
return;
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
if (Comment != null) config["comment"] = Comment;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new UserService();
|
||||
|
||||
WriteVerbose($"Updating group '{GroupId}'...");
|
||||
service.UpdateGroup(session, GroupId, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Management.Automation;
|
||||
using System.Runtime.InteropServices;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Changes a Proxmox VE user's password.</para>
|
||||
/// <para type="description">
|
||||
/// Updates the password for the specified user via the /access/password endpoint.
|
||||
/// The password is accepted as a SecureString for safe handling in PowerShell.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PvePassword", SupportsShouldProcess = true)]
|
||||
public class SetPvePasswordCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The user identifier in "user@realm" format.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The user ID in user@realm format.")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The new password for the user.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The new password for the user.")]
|
||||
public System.Security.SecureString Password { get; set; } = new System.Security.SecureString();
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess(UserId, "Set PVE Password"))
|
||||
return;
|
||||
|
||||
var ptr = Marshal.SecureStringToGlobalAllocUnicode(Password);
|
||||
string plainPassword;
|
||||
try
|
||||
{
|
||||
plainPassword = Marshal.PtrToStringUni(ptr) ?? string.Empty;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.ZeroFreeGlobalAllocUnicode(ptr);
|
||||
}
|
||||
|
||||
var session = GetSession();
|
||||
var service = new UserService();
|
||||
|
||||
WriteVerbose($"Changing password for user '{UserId}'...");
|
||||
service.ChangePassword(session, UserId, plainPassword);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates a Proxmox VE role's privileges.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies the privilege set for an existing custom role.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveRole", SupportsShouldProcess = true)]
|
||||
public sealed class SetPveRoleCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The role identifier to update.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The role identifier.")]
|
||||
public string RoleId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Comma-separated list of privileges to assign to the role.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, HelpMessage = "Comma-separated list of privileges (e.g. 'VM.Audit,VM.Console').")]
|
||||
public string Privileges { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"role '{RoleId}'", "Set"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new UserService();
|
||||
|
||||
WriteVerbose($"Updating role '{RoleId}'...");
|
||||
service.UpdateRole(session, RoleId, Privileges);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Vms
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Gets filesystem information from the QEMU guest agent.</para>
|
||||
/// <para type="description">
|
||||
/// Queries the QEMU guest agent running inside the specified VM for its filesystem
|
||||
/// information, including mount points, filesystem types, and usage statistics.
|
||||
/// The guest agent must be installed and running inside the VM.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveVmGuestFsInfo")]
|
||||
[OutputType(typeof(PveGuestFsInfo))]
|
||||
public sealed class GetPveVmGuestFsInfoCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The VM identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
|
||||
WriteVerbose($"Getting guest filesystem info for VM {VmId}...");
|
||||
var service = new VmService();
|
||||
var fsInfos = service.GetGuestFsInfo(session, Node, VmId);
|
||||
|
||||
foreach (var fs in fsInfos)
|
||||
WriteObject(fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Vms
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Gets OS information from the QEMU guest agent.</para>
|
||||
/// <para type="description">
|
||||
/// Queries the QEMU guest agent running inside the specified VM for its operating system
|
||||
/// information, including OS name, kernel version, and machine type.
|
||||
/// The guest agent must be installed and running inside the VM.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveVmGuestOsInfo")]
|
||||
[OutputType(typeof(PveGuestOsInfo))]
|
||||
public sealed class GetPveVmGuestOsInfoCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The VM identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
|
||||
WriteVerbose($"Getting guest OS info for VM {VmId}...");
|
||||
var service = new VmService();
|
||||
var osInfo = service.GetGuestOsInfo(session, Node, VmId);
|
||||
|
||||
if (osInfo != null)
|
||||
WriteObject(osInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Vms
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Invokes an fstrim operation inside the guest via the QEMU guest agent.</para>
|
||||
/// <para type="description">
|
||||
/// Triggers an fstrim (filesystem trim) operation inside the guest operating system
|
||||
/// using the QEMU guest agent. This reclaims unused space on thin-provisioned storage.
|
||||
/// The guest agent must be installed and running inside the VM.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsLifecycle.Invoke, "PveVmGuestFsTrim", SupportsShouldProcess = true)]
|
||||
public sealed class InvokePveVmGuestFsTrimCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The VM identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"VM {VmId} on node '{Node}'", "Invoke-PveVmGuestFsTrim"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
|
||||
WriteVerbose($"Invoking fstrim on VM {VmId} via guest agent...");
|
||||
var service = new VmService();
|
||||
service.GuestFsTrim(session, Node, VmId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Vms
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Moves a VM disk to a different storage.</para>
|
||||
/// <para type="description">
|
||||
/// Moves the specified disk on a QEMU/KVM virtual machine to a different storage backend
|
||||
/// via the Proxmox VE API. Optionally deletes the original disk after a successful move.
|
||||
/// Use -Wait to block until the move task completes.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Move, "PveVmDisk", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(PveTask))]
|
||||
public sealed class MovePveVmDiskCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The node on which the VM resides.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The ID of the VM whose disk should be moved.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The disk slot to move (e.g., "scsi0", "virtio0").</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The disk slot to move (e.g. scsi0, virtio0).")]
|
||||
public string Disk { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The target storage to move the disk to.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The target storage identifier.")]
|
||||
public string Storage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The target disk format.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Target disk format (raw, qcow2, vmdk).")]
|
||||
[ValidateSet("raw", "qcow2", "vmdk")]
|
||||
public string? Format { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When specified, deletes the original disk after a successful move. Default is true.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Delete the original disk after move (default true).")]
|
||||
public SwitchParameter Delete { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When specified, waits for the move task to complete before returning.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")]
|
||||
public SwitchParameter Wait { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"Disk '{Disk}' on VM {VmId} (node '{Node}') to storage '{Storage}'", "Move-PveVmDisk"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var vmService = new VmService();
|
||||
|
||||
WriteVerbose($"Moving disk '{Disk}' on VM {VmId} to storage '{Storage}'...");
|
||||
var task = vmService.MoveDisk(session, Node, VmId, Disk, Storage, Format, Delete.IsPresent);
|
||||
|
||||
if (Wait.IsPresent)
|
||||
{
|
||||
var taskService = new TaskService();
|
||||
task = taskService.WaitForTask(session, Node, task.Upid, null, null, null);
|
||||
}
|
||||
|
||||
WriteObject(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Vms
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Reads a file from the guest filesystem via the QEMU guest agent.</para>
|
||||
/// <para type="description">
|
||||
/// Reads the contents of a file inside the guest operating system using the QEMU guest agent.
|
||||
/// The guest agent must be installed and running inside the VM.
|
||||
/// Returns the file content as a string.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommunications.Read, "PveVmGuestFile")]
|
||||
[OutputType(typeof(string))]
|
||||
public sealed class ReadPveVmGuestFileCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The VM identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The path to the file inside the guest to read.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The file path inside the guest to read.")]
|
||||
public string File { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
|
||||
WriteVerbose($"Reading file '{File}' from VM {VmId} via guest agent...");
|
||||
var service = new VmService();
|
||||
var content = service.ReadGuestFile(session, Node, VmId, File);
|
||||
|
||||
WriteObject(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Vms
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Removes (unlinks) disks from a QEMU/KVM virtual machine.</para>
|
||||
/// <para type="description">
|
||||
/// Detaches and optionally destroys one or more disks from a virtual machine
|
||||
/// via the Proxmox VE API. This operation is destructive when -Force is used.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "PveVmDisk",
|
||||
SupportsShouldProcess = true,
|
||||
ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemovePveVmDiskCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The node on which the VM resides.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The ID of the VM whose disks should be removed.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Comma-separated list of disk IDs to unlink (e.g., "scsi0,scsi1").</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "Comma-separated list of disk IDs to unlink (e.g. scsi0,scsi1).")]
|
||||
public string IdList { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When specified, forces removal of the disk images from storage.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Force removal of disk images from storage.")]
|
||||
public SwitchParameter Force { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"Disks '{IdList}' on VM {VmId} (node '{Node}')", "Remove-PveVmDisk"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var vmService = new VmService();
|
||||
|
||||
WriteVerbose($"Unlinking disks '{IdList}' from VM {VmId}...");
|
||||
vmService.UnlinkDisk(session, Node, VmId, IdList, Force.IsPresent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Vms
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Sets a user password inside the guest via the QEMU guest agent.</para>
|
||||
/// <para type="description">
|
||||
/// Changes a user's password inside the guest operating system using the QEMU guest agent.
|
||||
/// The guest agent must be installed and running inside the VM.
|
||||
/// Use -Crypted if the password is already in a hashed/crypted format.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveVmGuestPassword", SupportsShouldProcess = true)]
|
||||
public sealed class SetPveVmGuestPasswordCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The VM identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The username whose password should be changed.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The username whose password to set.")]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The new password for the user.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The new password.")]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When specified, indicates the password is already in crypted/hashed format.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Indicates the password is already crypted/hashed.")]
|
||||
public SwitchParameter Crypted { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"User '{Username}' on VM {VmId} (node '{Node}')", "Set-PveVmGuestPassword"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
|
||||
WriteVerbose($"Setting password for user '{Username}' on VM {VmId} via guest agent...");
|
||||
var service = new VmService();
|
||||
service.SetGuestPassword(session, Node, VmId, Username, Password, Crypted.IsPresent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Vms
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Writes content to a file on the guest filesystem via the QEMU guest agent.</para>
|
||||
/// <para type="description">
|
||||
/// Writes the specified content to a file inside the guest operating system using the QEMU guest agent.
|
||||
/// The guest agent must be installed and running inside the VM.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommunications.Write, "PveVmGuestFile", SupportsShouldProcess = true)]
|
||||
public sealed class WritePveVmGuestFileCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The Proxmox VE node name.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
|
||||
public string Node { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The VM identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The path to the file inside the guest to write.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The file path inside the guest to write.")]
|
||||
public string File { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The content to write to the file.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The content to write to the file.")]
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"File '{File}' on VM {VmId} (node '{Node}')", "Write-PveVmGuestFile"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
|
||||
WriteVerbose($"Writing to file '{File}' on VM {VmId} via guest agent...");
|
||||
var service = new VmService();
|
||||
service.WriteGuestFile(session, Node, VmId, File, Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -944,5 +944,417 @@
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Cluster.PveClusterResource -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Cluster.PveClusterResource</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Cluster.PveClusterResource</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Type</Label>
|
||||
<Width>8</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Name</Label>
|
||||
<Width>20</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Status</Label>
|
||||
<Width>10</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Node</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>VmId</Label>
|
||||
<Width>8</Width>
|
||||
<Alignment>Right</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Cpu</Label>
|
||||
<Width>8</Width>
|
||||
<Alignment>Right</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>MaxMem</Label>
|
||||
<Width>12</Width>
|
||||
<Alignment>Right</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Pool</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Type</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Name</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Status</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Node</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>VmId</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.Cpu -ne $null) { [math]::Round($_.Cpu * 100, 1).ToString() + '%' } else { 'N/A' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.MaxMem) { [math]::Round($_.MaxMem / 1GB, 1).ToString() + 'G' } else { 'N/A' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Pool</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Cluster.PvePool -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Cluster.PvePool</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Cluster.PvePool</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>PoolId</Label>
|
||||
<Width>25</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Comment</Label>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>PoolId</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Comment</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Vms.PveGuestOsInfo -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Vms.PveGuestOsInfo</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Vms.PveGuestOsInfo</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Name</Label>
|
||||
<Width>25</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>KernelRelease</Label>
|
||||
<Width>25</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Machine</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Name</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>KernelRelease</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Machine</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Vms.PveGuestFsInfo -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Vms.PveGuestFsInfo</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Vms.PveGuestFsInfo</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Name</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>MountPoint</Label>
|
||||
<Width>20</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Type</Label>
|
||||
<Width>10</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>TotalBytes</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Right</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>UsedBytes</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Right</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Name</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>MountPoint</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Type</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.TotalBytes) { [math]::Round($_.TotalBytes / 1GB, 1).ToString() + ' GB' } else { 'N/A' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.UsedBytes) { [math]::Round($_.UsedBytes / 1GB, 1).ToString() + ' GB' } else { 'N/A' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Containers.PveContainerInterface -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Containers.PveContainerInterface</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Containers.PveContainerInterface</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Name</Label>
|
||||
<Width>10</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>HwAddr</Label>
|
||||
<Width>20</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Inet</Label>
|
||||
<Width>20</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Inet6</Label>
|
||||
<Width>30</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Name</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>HwAddr</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Inet</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Inet6</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Storage.PveStorageStatus -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Storage.PveStorageStatus</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Storage.PveStorageStatus</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Type</Label>
|
||||
<Width>10</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Total</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Right</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Used</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Right</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Available</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Right</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Active</Label>
|
||||
<Width>8</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Type</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.Total) { [math]::Round($_.Total / 1GB, 1).ToString() + ' GB' } else { 'N/A' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.Used) { [math]::Round($_.Used / 1GB, 1).ToString() + ' GB' } else { 'N/A' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.Available) { [math]::Round($_.Available / 1GB, 1).ToString() + ' GB' } else { 'N/A' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.Active) { 'Yes' } else { 'No' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Users.PveGroup -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Users.PveGroup</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Users.PveGroup</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>GroupId</Label>
|
||||
<Width>25</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Comment</Label>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Users</Label>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>GroupId</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Comment</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Users</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Users.PveDomain -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Users.PveDomain</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Users.PveDomain</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Realm</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Type</Label>
|
||||
<Width>10</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Comment</Label>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Default</Label>
|
||||
<Width>8</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Realm</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Type</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Comment</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.Default) { 'Yes' } else { 'No' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
</ViewDefinitions>
|
||||
</Configuration>
|
||||
|
||||
@@ -215,7 +215,86 @@
|
||||
# SDN — Controller
|
||||
'Get-PveSdnController',
|
||||
'New-PveSdnController',
|
||||
'Remove-PveSdnController'
|
||||
'Remove-PveSdnController',
|
||||
|
||||
# SDN — Update / Apply
|
||||
'Set-PveSdnZone',
|
||||
'Set-PveSdnVnet',
|
||||
'Set-PveSdnSubnet',
|
||||
'Set-PveSdnController',
|
||||
'Set-PveSdnIpam',
|
||||
'Set-PveSdnDns',
|
||||
'Invoke-PveSdnApply',
|
||||
|
||||
# Role / Storage / Token — Update
|
||||
'Set-PveRole',
|
||||
'Set-PveStorage',
|
||||
'Set-PveApiToken',
|
||||
|
||||
# Cluster
|
||||
'Get-PveClusterResource',
|
||||
|
||||
# Tasks
|
||||
'Get-PveTaskList',
|
||||
'Stop-PveTask',
|
||||
|
||||
# Pools
|
||||
'Get-PvePool',
|
||||
'New-PvePool',
|
||||
'Set-PvePool',
|
||||
'Remove-PvePool',
|
||||
|
||||
# Backup Compliance
|
||||
'Get-PveBackupInfo',
|
||||
|
||||
# VM Disk Operations
|
||||
'Move-PveVmDisk',
|
||||
'Remove-PveVmDisk',
|
||||
|
||||
# VM Guest Agent Extensions
|
||||
'Get-PveVmGuestOsInfo',
|
||||
'Get-PveVmGuestFsInfo',
|
||||
'Read-PveVmGuestFile',
|
||||
'Write-PveVmGuestFile',
|
||||
'Set-PveVmGuestPassword',
|
||||
'Invoke-PveVmGuestFsTrim',
|
||||
|
||||
# Container Gaps
|
||||
'Suspend-PveContainer',
|
||||
'Resume-PveContainer',
|
||||
'Resize-PveContainerDisk',
|
||||
'New-PveContainerTemplate',
|
||||
'Move-PveContainerVolume',
|
||||
'Get-PveContainerInterface',
|
||||
|
||||
# Storage Content Management
|
||||
'Get-PveStorageStatus',
|
||||
'Remove-PveStorageContent',
|
||||
'Set-PveStorageContent',
|
||||
'New-PveStorageDisk',
|
||||
|
||||
# Node Operations
|
||||
'Get-PveNodeConfig',
|
||||
'Set-PveNodeConfig',
|
||||
'Get-PveNodeDns',
|
||||
'Set-PveNodeDns',
|
||||
'Start-PveNodeVms',
|
||||
'Stop-PveNodeVms',
|
||||
|
||||
# Access — Groups
|
||||
'Get-PveGroup',
|
||||
'New-PveGroup',
|
||||
'Set-PveGroup',
|
||||
'Remove-PveGroup',
|
||||
|
||||
# Access — Domains / Realms
|
||||
'Get-PveDomain',
|
||||
'New-PveDomain',
|
||||
'Set-PveDomain',
|
||||
'Remove-PveDomain',
|
||||
|
||||
# Access — Password
|
||||
'Set-PvePassword'
|
||||
)
|
||||
|
||||
# Variables to export from this module
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for backup info cmdlets:
|
||||
Get-PveBackupInfo.
|
||||
|
||||
All tests are fully offline — no live Proxmox VE target is required.
|
||||
If a cmdlet is not yet compiled the test is marked Skipped.
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
$script:Availability = @{}
|
||||
foreach ($name in @('Get-PveBackupInfo')) {
|
||||
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Skip-IfMissing([string]$Name) {
|
||||
if (-not $script:Availability[$Name]) {
|
||||
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveBackupInfo
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveBackupInfo' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveBackupInfo' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveBackupInfo'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveBackupInfo'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveBackupInfo'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveBackupInfo'
|
||||
{ Get-PveBackupInfo -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for cluster cmdlets:
|
||||
Get-PveClusterResource.
|
||||
|
||||
All tests are fully offline — no live Proxmox VE target is required.
|
||||
If a cmdlet is not yet compiled the test is marked Skipped.
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
$script:Availability = @{}
|
||||
foreach ($name in @('Get-PveClusterResource')) {
|
||||
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Skip-IfMissing([string]$Name) {
|
||||
if (-not $script:Availability[$Name]) {
|
||||
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveClusterResource
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveClusterResource' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveClusterResource' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveClusterResource'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveClusterResource'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional Type parameter' {
|
||||
Skip-IfMissing 'Get-PveClusterResource'
|
||||
$script:Cmd.Parameters.ContainsKey('Type') | Should -BeTrue
|
||||
}
|
||||
It 'Should have optional Node parameter' {
|
||||
Skip-IfMissing 'Get-PveClusterResource'
|
||||
$script:Cmd.Parameters.ContainsKey('Node') | Should -BeTrue
|
||||
}
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveClusterResource'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveClusterResource'
|
||||
{ Get-PveClusterResource -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for container gap cmdlets:
|
||||
Suspend-PveContainer, Resume-PveContainer, Resize-PveContainerDisk,
|
||||
New-PveContainerTemplate, Move-PveContainerVolume, Get-PveContainerInterface.
|
||||
|
||||
All tests are fully offline — no live Proxmox VE target is required.
|
||||
If a cmdlet is not yet compiled the test is marked Skipped.
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
$script:Availability = @{}
|
||||
foreach ($name in @(
|
||||
'Suspend-PveContainer', 'Resume-PveContainer', 'Resize-PveContainerDisk',
|
||||
'New-PveContainerTemplate', 'Move-PveContainerVolume', 'Get-PveContainerInterface'
|
||||
)) {
|
||||
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Skip-IfMissing([string]$Name) {
|
||||
if (-not $script:Availability[$Name]) {
|
||||
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Suspend-PveContainer
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Suspend-PveContainer' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Suspend-PveContainer' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Suspend-PveContainer'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Suspend-PveContainer'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Suspend-PveContainer'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Suspend-PveContainer'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Suspend-PveContainer'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Suspend-PveContainer'
|
||||
{ Suspend-PveContainer -Node 'pve' -VmId 100 -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Resume-PveContainer
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Resume-PveContainer' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Resume-PveContainer' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Resume-PveContainer'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Resume-PveContainer'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Resume-PveContainer'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Resume-PveContainer'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Resume-PveContainer'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Resume-PveContainer'
|
||||
{ Resume-PveContainer -Node 'pve' -VmId 100 -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Resize-PveContainerDisk
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Resize-PveContainerDisk' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Resize-PveContainerDisk' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Resize-PveContainerDisk'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Resize-PveContainerDisk'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Resize-PveContainerDisk'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Resize-PveContainerDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Resize-PveContainerDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Disk should be Mandatory' {
|
||||
Skip-IfMissing 'Resize-PveContainerDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['Disk'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Size should be Mandatory' {
|
||||
Skip-IfMissing 'Resize-PveContainerDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['Size'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Resize-PveContainerDisk'
|
||||
{ Resize-PveContainerDisk -Node 'pve' -VmId 100 -Disk 'rootfs' -Size '+5G' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveContainerTemplate
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveContainerTemplate' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveContainerTemplate' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveContainerTemplate'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveContainerTemplate'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'New-PveContainerTemplate'
|
||||
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
|
||||
[System.Management.Automation.CmdletAttribute], $false) |
|
||||
Select-Object -First 1
|
||||
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveContainerTemplate'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveContainerTemplate'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PveContainerTemplate'
|
||||
{ New-PveContainerTemplate -Node 'pve' -VmId 100 -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Move-PveContainerVolume
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Move-PveContainerVolume' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Move-PveContainerVolume' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Move-PveContainerVolume'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Move-PveContainerVolume'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Move-PveContainerVolume'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Move-PveContainerVolume'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Move-PveContainerVolume'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Volume should be Mandatory' {
|
||||
Skip-IfMissing 'Move-PveContainerVolume'
|
||||
$isMandatory = $script:Cmd.Parameters['Volume'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Storage should be Mandatory' {
|
||||
Skip-IfMissing 'Move-PveContainerVolume'
|
||||
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Move-PveContainerVolume'
|
||||
{ Move-PveContainerVolume -Node 'pve' -VmId 100 -Volume 'rootfs' -Storage 'local-lvm' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveContainerInterface
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveContainerInterface' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveContainerInterface' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveContainerInterface'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveContainerInterface'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveContainerInterface'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveContainerInterface'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveContainerInterface'
|
||||
{ Get-PveContainerInterface -Node 'pve' -VmId 100 -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for SDN Set cmdlets and related mutating cmdlets:
|
||||
Set-PveSdnZone, Set-PveSdnVnet, Set-PveSdnSubnet, Set-PveSdnController,
|
||||
Set-PveSdnIpam, Set-PveSdnDns, Invoke-PveSdnApply, Set-PveRole, Set-PveApiToken.
|
||||
|
||||
All tests are fully offline — no live Proxmox VE target is required.
|
||||
If a cmdlet is not yet compiled the test is marked Skipped.
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
$script:Availability = @{}
|
||||
foreach ($name in @(
|
||||
'Set-PveSdnZone', 'Set-PveSdnVnet', 'Set-PveSdnSubnet', 'Set-PveSdnController',
|
||||
'Set-PveSdnIpam', 'Set-PveSdnDns', 'Invoke-PveSdnApply', 'Set-PveRole', 'Set-PveApiToken'
|
||||
)) {
|
||||
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Skip-IfMissing([string]$Name) {
|
||||
if (-not $script:Availability[$Name]) {
|
||||
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveSdnZone
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveSdnZone' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnZone' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveSdnZone'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveSdnZone'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveSdnZone'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Zone should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveSdnZone'
|
||||
$isMandatory = $script:Cmd.Parameters['Zone'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveSdnZone'
|
||||
{ Set-PveSdnZone -Zone 'testzone' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveSdnVnet
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveSdnVnet' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnVnet' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveSdnVnet'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveSdnVnet'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveSdnVnet'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Vnet should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveSdnVnet'
|
||||
$isMandatory = $script:Cmd.Parameters['Vnet'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveSdnVnet'
|
||||
{ Set-PveSdnVnet -Vnet 'testvnet' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveSdnSubnet
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveSdnSubnet' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnSubnet' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveSdnSubnet'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveSdnSubnet'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveSdnSubnet'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Vnet should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveSdnSubnet'
|
||||
$isMandatory = $script:Cmd.Parameters['Vnet'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Subnet should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveSdnSubnet'
|
||||
$isMandatory = $script:Cmd.Parameters['Subnet'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveSdnSubnet'
|
||||
{ Set-PveSdnSubnet -Vnet 'testvnet' -Subnet '10.0.0.0/24' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveSdnController
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveSdnController' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnController' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveSdnController'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveSdnController'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveSdnController'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Controller should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveSdnController'
|
||||
$isMandatory = $script:Cmd.Parameters['Controller'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveSdnController'
|
||||
{ Set-PveSdnController -Controller 'testctrl' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveSdnIpam
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveSdnIpam' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnIpam' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveSdnIpam'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveSdnIpam'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveSdnIpam'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Ipam should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveSdnIpam'
|
||||
$isMandatory = $script:Cmd.Parameters['Ipam'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveSdnIpam'
|
||||
{ Set-PveSdnIpam -Ipam 'testipam' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveSdnDns
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveSdnDns' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnDns' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveSdnDns'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveSdnDns'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveSdnDns'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Dns should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveSdnDns'
|
||||
$isMandatory = $script:Cmd.Parameters['Dns'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveSdnDns'
|
||||
{ Set-PveSdnDns -Dns 'testdns' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Invoke-PveSdnApply
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Invoke-PveSdnApply' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Invoke-PveSdnApply' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Invoke-PveSdnApply'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Invoke-PveSdnApply'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Invoke-PveSdnApply'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Invoke-PveSdnApply'
|
||||
{ Invoke-PveSdnApply -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveRole
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveRole' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveRole' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveRole'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveRole'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveRole'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'RoleId should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveRole'
|
||||
$isMandatory = $script:Cmd.Parameters['RoleId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Privileges should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveRole'
|
||||
$isMandatory = $script:Cmd.Parameters['Privileges'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveRole'
|
||||
{ Set-PveRole -RoleId 'testrole' -Privileges 'VM.Audit' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveApiToken
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveApiToken' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveApiToken' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveApiToken'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveApiToken'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveApiToken'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'UserId should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveApiToken'
|
||||
$isMandatory = $script:Cmd.Parameters['UserId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'TokenId should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveApiToken'
|
||||
$isMandatory = $script:Cmd.Parameters['TokenId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveApiToken'
|
||||
{ Set-PveApiToken -UserId 'user@pam' -TokenId 'testtoken' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for node operation cmdlets:
|
||||
Get-PveNodeConfig, Set-PveNodeConfig, Get-PveNodeDns, Set-PveNodeDns,
|
||||
Start-PveNodeVms, Stop-PveNodeVms.
|
||||
|
||||
All tests are fully offline — no live Proxmox VE target is required.
|
||||
If a cmdlet is not yet compiled the test is marked Skipped.
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
$script:Availability = @{}
|
||||
foreach ($name in @(
|
||||
'Get-PveNodeConfig', 'Set-PveNodeConfig', 'Get-PveNodeDns', 'Set-PveNodeDns',
|
||||
'Start-PveNodeVms', 'Stop-PveNodeVms'
|
||||
)) {
|
||||
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Skip-IfMissing([string]$Name) {
|
||||
if (-not $script:Availability[$Name]) {
|
||||
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveNodeConfig
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveNodeConfig' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveNodeConfig' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveNodeConfig'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveNodeConfig'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveNodeConfig'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveNodeConfig'
|
||||
{ Get-PveNodeConfig -Node 'pve' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveNodeConfig
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveNodeConfig' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveNodeConfig' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveNodeConfig'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveNodeConfig'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveNodeConfig'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveNodeConfig'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveNodeConfig'
|
||||
{ Set-PveNodeConfig -Node 'pve' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveNodeDns
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveNodeDns' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveNodeDns' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveNodeDns'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveNodeDns'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveNodeDns'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveNodeDns'
|
||||
{ Get-PveNodeDns -Node 'pve' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveNodeDns
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveNodeDns' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveNodeDns' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveNodeDns'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveNodeDns'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveNodeDns'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveNodeDns'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Search should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveNodeDns'
|
||||
$isMandatory = $script:Cmd.Parameters['Search'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveNodeDns'
|
||||
{ Set-PveNodeDns -Node 'pve' -Search 'example.com' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Start-PveNodeVms
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Start-PveNodeVms' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Start-PveNodeVms' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Start-PveNodeVms'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Start-PveNodeVms'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Start-PveNodeVms'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Start-PveNodeVms'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Start-PveNodeVms'
|
||||
{ Start-PveNodeVms -Node 'pve' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stop-PveNodeVms
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Stop-PveNodeVms' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Stop-PveNodeVms' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Stop-PveNodeVms'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Stop-PveNodeVms'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Stop-PveNodeVms'
|
||||
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
|
||||
[System.Management.Automation.CmdletAttribute], $false) |
|
||||
Select-Object -First 1
|
||||
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Stop-PveNodeVms'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Stop-PveNodeVms'
|
||||
{ Stop-PveNodeVms -Node 'pve' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for pool cmdlets:
|
||||
Get-PvePool, New-PvePool, Set-PvePool, Remove-PvePool.
|
||||
|
||||
All tests are fully offline — no live Proxmox VE target is required.
|
||||
If a cmdlet is not yet compiled the test is marked Skipped.
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
$script:Availability = @{}
|
||||
foreach ($name in @('Get-PvePool', 'New-PvePool', 'Set-PvePool', 'Remove-PvePool')) {
|
||||
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Skip-IfMissing([string]$Name) {
|
||||
if (-not $script:Availability[$Name]) {
|
||||
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PvePool
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PvePool' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PvePool' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PvePool'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PvePool'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional PoolId parameter' {
|
||||
Skip-IfMissing 'Get-PvePool'
|
||||
$script:Cmd.Parameters.ContainsKey('PoolId') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PvePool'
|
||||
{ Get-PvePool -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PvePool
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PvePool' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PvePool' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PvePool'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PvePool'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'PoolId should be Mandatory' {
|
||||
Skip-IfMissing 'New-PvePool'
|
||||
$isMandatory = $script:Cmd.Parameters['PoolId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PvePool'
|
||||
{ New-PvePool -PoolId 'testpool' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PvePool
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PvePool' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PvePool' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PvePool'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PvePool'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'PoolId should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PvePool'
|
||||
$isMandatory = $script:Cmd.Parameters['PoolId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PvePool'
|
||||
{ Set-PvePool -PoolId 'testpool' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PvePool
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PvePool' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PvePool' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PvePool'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PvePool'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PvePool'
|
||||
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
|
||||
[System.Management.Automation.CmdletAttribute], $false) |
|
||||
Select-Object -First 1
|
||||
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'PoolId should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PvePool'
|
||||
$isMandatory = $script:Cmd.Parameters['PoolId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Remove-PvePool'
|
||||
{ Remove-PvePool -PoolId 'testpool' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for storage management cmdlets:
|
||||
Set-PveStorage, Get-PveStorageStatus, Remove-PveStorageContent,
|
||||
Set-PveStorageContent, New-PveStorageDisk.
|
||||
|
||||
All tests are fully offline — no live Proxmox VE target is required.
|
||||
If a cmdlet is not yet compiled the test is marked Skipped.
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
$script:Availability = @{}
|
||||
foreach ($name in @(
|
||||
'Set-PveStorage', 'Get-PveStorageStatus', 'Remove-PveStorageContent',
|
||||
'Set-PveStorageContent', 'New-PveStorageDisk'
|
||||
)) {
|
||||
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Skip-IfMissing([string]$Name) {
|
||||
if (-not $script:Availability[$Name]) {
|
||||
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveStorage
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveStorage' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveStorage' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveStorage'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveStorage'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveStorage'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Storage should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveStorage'
|
||||
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveStorage'
|
||||
{ Set-PveStorage -Storage 'local' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveStorageStatus
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveStorageStatus' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveStorageStatus' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveStorageStatus'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveStorageStatus'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveStorageStatus'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Storage should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveStorageStatus'
|
||||
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveStorageStatus'
|
||||
{ Get-PveStorageStatus -Node 'pve' -Storage 'local' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveStorageContent
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveStorageContent' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveStorageContent' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveStorageContent'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveStorageContent'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveStorageContent'
|
||||
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
|
||||
[System.Management.Automation.CmdletAttribute], $false) |
|
||||
Select-Object -First 1
|
||||
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveStorageContent'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Storage should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveStorageContent'
|
||||
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Volume should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveStorageContent'
|
||||
$isMandatory = $script:Cmd.Parameters['Volume'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Remove-PveStorageContent'
|
||||
{ Remove-PveStorageContent -Node 'pve' -Storage 'local' -Volume 'local:iso/test.iso' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveStorageContent
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveStorageContent' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveStorageContent' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveStorageContent'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveStorageContent'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveStorageContent'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveStorageContent'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Storage should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveStorageContent'
|
||||
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Volume should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveStorageContent'
|
||||
$isMandatory = $script:Cmd.Parameters['Volume'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveStorageContent'
|
||||
{ Set-PveStorageContent -Node 'pve' -Storage 'local' -Volume 'local:iso/test.iso' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveStorageDisk
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveStorageDisk' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveStorageDisk' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveStorageDisk'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'New-PveStorageDisk'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveStorageDisk'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveStorageDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Storage should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveStorageDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Filename should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveStorageDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['Filename'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Size should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveStorageDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['Size'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PveStorageDisk'
|
||||
{ New-PveStorageDisk -Node 'pve' -Storage 'local' -Filename 'vm-100-disk-1' -Size '32G' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for task list cmdlets:
|
||||
Get-PveTaskList, Stop-PveTask.
|
||||
|
||||
All tests are fully offline — no live Proxmox VE target is required.
|
||||
If a cmdlet is not yet compiled the test is marked Skipped.
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
$script:Availability = @{}
|
||||
foreach ($name in @('Get-PveTaskList', 'Stop-PveTask')) {
|
||||
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Skip-IfMissing([string]$Name) {
|
||||
if (-not $script:Availability[$Name]) {
|
||||
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveTaskList
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveTaskList' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveTaskList' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveTaskList'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveTaskList'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveTaskList'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional VmId parameter' {
|
||||
Skip-IfMissing 'Get-PveTaskList'
|
||||
$script:Cmd.Parameters.ContainsKey('VmId') | Should -BeTrue
|
||||
}
|
||||
It 'Should have optional Limit parameter' {
|
||||
Skip-IfMissing 'Get-PveTaskList'
|
||||
$script:Cmd.Parameters.ContainsKey('Limit') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveTaskList'
|
||||
{ Get-PveTaskList -Node 'pve' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stop-PveTask
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Stop-PveTask' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Stop-PveTask' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Stop-PveTask'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Stop-PveTask'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Stop-PveTask'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Stop-PveTask'
|
||||
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
|
||||
[System.Management.Automation.CmdletAttribute], $false) |
|
||||
Select-Object -First 1
|
||||
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Stop-PveTask'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Upid should be Mandatory' {
|
||||
Skip-IfMissing 'Stop-PveTask'
|
||||
$isMandatory = $script:Cmd.Parameters['Upid'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Stop-PveTask'
|
||||
{ Stop-PveTask -Node 'pve' -Upid 'UPID:pve:00000001:00000000:00000000:test::root@pam:' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for access management cmdlets:
|
||||
Get-PveGroup, New-PveGroup, Set-PveGroup, Remove-PveGroup,
|
||||
Get-PveDomain, New-PveDomain, Set-PveDomain, Remove-PveDomain, Set-PvePassword.
|
||||
|
||||
All tests are fully offline — no live Proxmox VE target is required.
|
||||
If a cmdlet is not yet compiled the test is marked Skipped.
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
$script:Availability = @{}
|
||||
foreach ($name in @(
|
||||
'Get-PveGroup', 'New-PveGroup', 'Set-PveGroup', 'Remove-PveGroup',
|
||||
'Get-PveDomain', 'New-PveDomain', 'Set-PveDomain', 'Remove-PveDomain', 'Set-PvePassword'
|
||||
)) {
|
||||
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Skip-IfMissing([string]$Name) {
|
||||
if (-not $script:Availability[$Name]) {
|
||||
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveGroup
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveGroup' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveGroup' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveGroup'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveGroup'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional GroupId parameter' {
|
||||
Skip-IfMissing 'Get-PveGroup'
|
||||
$script:Cmd.Parameters.ContainsKey('GroupId') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveGroup'
|
||||
{ Get-PveGroup -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveGroup
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveGroup' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveGroup' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveGroup'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveGroup'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'GroupId should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveGroup'
|
||||
$isMandatory = $script:Cmd.Parameters['GroupId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PveGroup'
|
||||
{ New-PveGroup -GroupId 'testgroup' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveGroup
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveGroup' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveGroup' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveGroup'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveGroup'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'GroupId should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveGroup'
|
||||
$isMandatory = $script:Cmd.Parameters['GroupId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveGroup'
|
||||
{ Set-PveGroup -GroupId 'testgroup' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveGroup
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveGroup' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveGroup' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveGroup'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveGroup'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveGroup'
|
||||
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
|
||||
[System.Management.Automation.CmdletAttribute], $false) |
|
||||
Select-Object -First 1
|
||||
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'GroupId should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveGroup'
|
||||
$isMandatory = $script:Cmd.Parameters['GroupId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Remove-PveGroup'
|
||||
{ Remove-PveGroup -GroupId 'testgroup' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveDomain
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveDomain' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveDomain' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveDomain'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveDomain'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional Realm parameter' {
|
||||
Skip-IfMissing 'Get-PveDomain'
|
||||
$script:Cmd.Parameters.ContainsKey('Realm') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveDomain'
|
||||
{ Get-PveDomain -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveDomain
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveDomain' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveDomain' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveDomain'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveDomain'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Realm should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveDomain'
|
||||
$isMandatory = $script:Cmd.Parameters['Realm'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Type should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveDomain'
|
||||
$isMandatory = $script:Cmd.Parameters['Type'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PveDomain'
|
||||
{ New-PveDomain -Realm 'testrealm' -Type 'pve' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveDomain
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveDomain' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveDomain' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveDomain'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveDomain'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Realm should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveDomain'
|
||||
$isMandatory = $script:Cmd.Parameters['Realm'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveDomain'
|
||||
{ Set-PveDomain -Realm 'testrealm' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveDomain
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveDomain' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveDomain' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveDomain'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveDomain'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveDomain'
|
||||
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
|
||||
[System.Management.Automation.CmdletAttribute], $false) |
|
||||
Select-Object -First 1
|
||||
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Realm should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveDomain'
|
||||
$isMandatory = $script:Cmd.Parameters['Realm'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Remove-PveDomain'
|
||||
{ Remove-PveDomain -Realm 'testrealm' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PvePassword
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PvePassword' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PvePassword' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PvePassword'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PvePassword'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PvePassword'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'UserId should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PvePassword'
|
||||
$isMandatory = $script:Cmd.Parameters['UserId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Password should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PvePassword'
|
||||
$isMandatory = $script:Cmd.Parameters['Password'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PvePassword'
|
||||
$secPw = ConvertTo-SecureString 'dummypassword' -AsPlainText -Force
|
||||
{ Set-PvePassword -UserId 'root@pam' -Password $secPw -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for extended guest agent cmdlets:
|
||||
Get-PveVmGuestOsInfo, Get-PveVmGuestFsInfo, Read-PveVmGuestFile,
|
||||
Write-PveVmGuestFile, Set-PveVmGuestPassword, Invoke-PveVmGuestFsTrim.
|
||||
|
||||
All tests are fully offline — no live Proxmox VE target is required.
|
||||
If a cmdlet is not yet compiled the test is marked Skipped.
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
$script:Availability = @{}
|
||||
foreach ($name in @(
|
||||
'Get-PveVmGuestOsInfo', 'Get-PveVmGuestFsInfo', 'Read-PveVmGuestFile',
|
||||
'Write-PveVmGuestFile', 'Set-PveVmGuestPassword', 'Invoke-PveVmGuestFsTrim'
|
||||
)) {
|
||||
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Skip-IfMissing([string]$Name) {
|
||||
if (-not $script:Availability[$Name]) {
|
||||
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveVmGuestOsInfo
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveVmGuestOsInfo' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveVmGuestOsInfo' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveVmGuestOsInfo'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveVmGuestOsInfo'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveVmGuestOsInfo'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveVmGuestOsInfo'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveVmGuestOsInfo'
|
||||
{ Get-PveVmGuestOsInfo -Node 'pve' -VmId 100 -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveVmGuestFsInfo
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveVmGuestFsInfo' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveVmGuestFsInfo' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveVmGuestFsInfo'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveVmGuestFsInfo'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveVmGuestFsInfo'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveVmGuestFsInfo'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveVmGuestFsInfo'
|
||||
{ Get-PveVmGuestFsInfo -Node 'pve' -VmId 100 -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Read-PveVmGuestFile
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Read-PveVmGuestFile' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Read-PveVmGuestFile' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Read-PveVmGuestFile'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Read-PveVmGuestFile'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Read-PveVmGuestFile'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Read-PveVmGuestFile'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'File should be Mandatory' {
|
||||
Skip-IfMissing 'Read-PveVmGuestFile'
|
||||
$isMandatory = $script:Cmd.Parameters['File'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Read-PveVmGuestFile'
|
||||
{ Read-PveVmGuestFile -Node 'pve' -VmId 100 -File '/etc/hostname' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Write-PveVmGuestFile
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Write-PveVmGuestFile' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Write-PveVmGuestFile' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Write-PveVmGuestFile'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Write-PveVmGuestFile'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Write-PveVmGuestFile'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Write-PveVmGuestFile'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Write-PveVmGuestFile'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'File should be Mandatory' {
|
||||
Skip-IfMissing 'Write-PveVmGuestFile'
|
||||
$isMandatory = $script:Cmd.Parameters['File'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Content should be Mandatory' {
|
||||
Skip-IfMissing 'Write-PveVmGuestFile'
|
||||
$isMandatory = $script:Cmd.Parameters['Content'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Write-PveVmGuestFile'
|
||||
{ Write-PveVmGuestFile -Node 'pve' -VmId 100 -File '/tmp/test.txt' -Content 'hello' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveVmGuestPassword
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveVmGuestPassword' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveVmGuestPassword' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveVmGuestPassword'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Set-PveVmGuestPassword'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveVmGuestPassword'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveVmGuestPassword'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveVmGuestPassword'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Username should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveVmGuestPassword'
|
||||
$isMandatory = $script:Cmd.Parameters['Username'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Password should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveVmGuestPassword'
|
||||
$isMandatory = $script:Cmd.Parameters['Password'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveVmGuestPassword'
|
||||
{ Set-PveVmGuestPassword -Node 'pve' -VmId 100 -Username 'root' -Password 'dummy' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Invoke-PveVmGuestFsTrim
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Invoke-PveVmGuestFsTrim' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Invoke-PveVmGuestFsTrim' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
|
||||
{ Invoke-PveVmGuestFsTrim -Node 'pve' -VmId 100 -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for VM disk operation cmdlets:
|
||||
Move-PveVmDisk, Remove-PveVmDisk.
|
||||
|
||||
All tests are fully offline — no live Proxmox VE target is required.
|
||||
If a cmdlet is not yet compiled the test is marked Skipped.
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
$script:Availability = @{}
|
||||
foreach ($name in @('Move-PveVmDisk', 'Remove-PveVmDisk')) {
|
||||
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Skip-IfMissing([string]$Name) {
|
||||
if (-not $script:Availability[$Name]) {
|
||||
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Move-PveVmDisk
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Move-PveVmDisk' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Move-PveVmDisk' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Move-PveVmDisk'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Move-PveVmDisk'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Move-PveVmDisk'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Move-PveVmDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Move-PveVmDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Disk should be Mandatory' {
|
||||
Skip-IfMissing 'Move-PveVmDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['Disk'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'Storage should be Mandatory' {
|
||||
Skip-IfMissing 'Move-PveVmDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Move-PveVmDisk'
|
||||
{ Move-PveVmDisk -Node 'pve' -VmId 100 -Disk 'scsi0' -Storage 'local-lvm' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveVmDisk
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveVmDisk' -Tag 'Unit' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveVmDisk' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveVmDisk'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveVmDisk'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveVmDisk'
|
||||
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
|
||||
[System.Management.Automation.CmdletAttribute], $false) |
|
||||
Select-Object -First 1
|
||||
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveVmDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveVmDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
It 'IdList should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveVmDisk'
|
||||
$isMandatory = $script:Cmd.Parameters['IdList'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Remove-PveVmDisk'
|
||||
{ Remove-PveVmDisk -Node 'pve' -VmId 100 -IdList 'scsi0' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user