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:
Clint Branham
2026-03-21 09:55:19 -05:00
parent ca4585f134
commit df4cae01bc
85 changed files with 7172 additions and 8 deletions
@@ -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
// -------------------------------------------------------------------------