mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-26 07:58:14 +00:00
feat: add container snapshot and SDN subnet cmdlets
Container snapshots (4 cmdlets): - Get-PveContainerSnapshot, New-PveContainerSnapshot - Remove-PveContainerSnapshot, Restore-PveContainerSnapshot - Service methods in ContainerService - Pester unit tests SDN subnet management (3 cmdlets): - Get-PveSdnSubnet, New-PveSdnSubnet, Remove-PveSdnSubnet - PveSdnSubnet model and NetworkService methods - Pester unit tests Also updates manifest (73 cmdlets), README cmdlet reference, and CHANGELOG. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+7
-4
@@ -7,6 +7,8 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.1.0-preview] - 2026-03-19
|
||||
|
||||
### Added
|
||||
|
||||
- Initial project structure and solution setup
|
||||
@@ -14,9 +16,10 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi
|
||||
- HTTP client with manual multipart ISO upload (bugzilla 7389 workaround)
|
||||
- Typed response models for PVE 8.x and 9.x API resources
|
||||
- Service layer for all resource domains
|
||||
- PowerShell cmdlets for all supported operations
|
||||
- 66 PowerShell cmdlets for VMs, containers, storage, networking, SDN, users, roles, permissions, API tokens, templates, cloud-init, snapshots, and tasks
|
||||
- QEMU guest agent cmdlets (Test-PveVmGuestAgent, Get-PveVmGuestNetwork, Invoke-PveVmGuestExec)
|
||||
- xUnit unit tests for core library
|
||||
- Pester 5 cmdlet tests across OS/PS version matrix
|
||||
- Integration test stubs for future live target testing
|
||||
- GitHub Actions CI/CD workflows
|
||||
- Pester 5 cmdlet tests across OS/PS version matrix (Windows PS 5.1, PS 7.5 on Windows/Linux/macOS)
|
||||
- Integration tests against live PVE 8 and PVE 9 instances via Terraform-provisioned nested VMs
|
||||
- GitHub Actions CI/CD workflows (build, unit tests, integration tests)
|
||||
- Format definitions for default table output on all PS versions
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# PSProxmoxVE
|
||||
|
||||
A production-grade C# binary PowerShell module for managing Proxmox VE environments. Built for internal worklab use.
|
||||
[](https://github.com/goodolclint/PSProxmoxVE/actions/workflows/build.yml)
|
||||
[](https://github.com/goodolclint/PSProxmoxVE/actions/workflows/unit-tests.yml)
|
||||
[](https://github.com/goodolclint/PSProxmoxVE/blob/main/LICENSE)
|
||||
|
||||
A production-grade C# binary PowerShell module for managing Proxmox VE environments.
|
||||
|
||||
## Supported Proxmox VE Versions
|
||||
|
||||
@@ -27,11 +31,12 @@ All public API surface compiles and functions correctly on both targets.
|
||||
|
||||
## Installation
|
||||
|
||||
This module is distributed via private GitHub releases (not PSGallery).
|
||||
|
||||
```powershell
|
||||
# Download the latest release from GitHub
|
||||
# Extract to a directory in your PSModulePath
|
||||
# Install from the PowerShell Gallery
|
||||
Install-Module -Name PSProxmoxVE -Scope CurrentUser
|
||||
|
||||
# Or install a prerelease version
|
||||
Install-Module -Name PSProxmoxVE -Scope CurrentUser -AllowPrerelease
|
||||
|
||||
# Verify installation
|
||||
Get-Module -ListAvailable PSProxmoxVE
|
||||
@@ -235,6 +240,10 @@ SDN management requires Proxmox VE 8.0 or later. Connected server is version 7.4
|
||||
| `Copy-PveContainer` | Clone a container |
|
||||
| `Get-PveContainerConfig` | Get container configuration |
|
||||
| `Set-PveContainerConfig` | Modify container configuration |
|
||||
| `Get-PveContainerSnapshot` | List container snapshots |
|
||||
| `New-PveContainerSnapshot` | Create a container snapshot |
|
||||
| `Remove-PveContainerSnapshot` | Delete a container snapshot |
|
||||
| `Restore-PveContainerSnapshot` | Rollback to a container snapshot |
|
||||
|
||||
### Storage
|
||||
| Cmdlet | Description |
|
||||
@@ -272,6 +281,9 @@ SDN management requires Proxmox VE 8.0 or later. Connected server is version 7.4
|
||||
| `Get-PveSdnVnet` | List SDN VNets |
|
||||
| `New-PveSdnVnet` | Create an SDN VNet |
|
||||
| `Remove-PveSdnVnet` | Delete an SDN VNet |
|
||||
| `Get-PveSdnSubnet` | List SDN subnets for a VNet |
|
||||
| `New-PveSdnSubnet` | Create an SDN subnet |
|
||||
| `Remove-PveSdnSubnet` | Delete an SDN subnet |
|
||||
|
||||
### Users & Permissions
|
||||
| Cmdlet | Description |
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Network;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Software-Defined Networking (SDN) subnet as returned by
|
||||
/// the /cluster/sdn/vnets/{vnet}/subnets endpoint.
|
||||
/// Available in Proxmox VE 8.0+.
|
||||
/// </summary>
|
||||
public class PveSdnSubnet
|
||||
{
|
||||
/// <summary>
|
||||
/// The subnet CIDR (e.g. "10.0.0.0/24" or "2001:db8::/64").
|
||||
/// </summary>
|
||||
[JsonPropertyName("subnet")]
|
||||
[JsonProperty("subnet")]
|
||||
public string Subnet { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The VNet this subnet belongs to.
|
||||
/// </summary>
|
||||
[JsonPropertyName("vnet")]
|
||||
[JsonProperty("vnet")]
|
||||
public string? Vnet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The gateway IP address for this subnet.
|
||||
/// </summary>
|
||||
[JsonPropertyName("gateway")]
|
||||
[JsonProperty("gateway")]
|
||||
public string? Gateway { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether SNAT (source NAT) is enabled for this subnet.
|
||||
/// </summary>
|
||||
[JsonPropertyName("snat")]
|
||||
[JsonProperty("snat")]
|
||||
public int? Snat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The DNS zone name for this subnet.
|
||||
/// </summary>
|
||||
[JsonPropertyName("dnszoneprefix")]
|
||||
[JsonProperty("dnszoneprefix")]
|
||||
public string? DnsZonePrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The DHCP range configuration for automatic IP assignment.
|
||||
/// </summary>
|
||||
[JsonPropertyName("dhcp-range")]
|
||||
[JsonProperty("dhcp-range")]
|
||||
public string? DhcpRange { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The subnet type identifier used internally by PVE.
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional comment or description.
|
||||
/// </summary>
|
||||
[JsonPropertyName("comments")]
|
||||
[JsonProperty("comments")]
|
||||
public string? Comments { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"SDN Subnet: {Subnet} | VNet: {Vnet ?? "N/A"} | "
|
||||
+ $"Gateway: {Gateway ?? "N/A"}";
|
||||
}
|
||||
}
|
||||
@@ -113,6 +113,90 @@ namespace PSProxmoxVE.Core.Services
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Snapshots
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns all snapshots for a container.
|
||||
/// </summary>
|
||||
public PveSnapshot[] GetContainerSnapshots(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}/snapshot")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveSnapshot[]>() ?? Array.Empty<PveSnapshot>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a snapshot of a container. Returns the task UPID.
|
||||
/// </summary>
|
||||
public PveTask CreateContainerSnapshot(
|
||||
PveSession session,
|
||||
string node,
|
||||
int vmid,
|
||||
string snapname,
|
||||
string? description = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
|
||||
|
||||
var formData = new Dictionary<string, string>
|
||||
{
|
||||
["snapname"] = snapname
|
||||
};
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
formData["description"] = description!;
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.PostAsync($"nodes/{node}/lxc/{vmid}/snapshot", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a snapshot from a container. Returns the task UPID.
|
||||
/// </summary>
|
||||
public PveTask RemoveContainerSnapshot(
|
||||
PveSession session,
|
||||
string node,
|
||||
int vmid,
|
||||
string snapname)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.DeleteAsync($"nodes/{node}/lxc/{vmid}/snapshot/{snapname}")
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rolls a container back to a snapshot. Returns the task UPID.
|
||||
/// </summary>
|
||||
public PveTask RollbackContainerSnapshot(
|
||||
PveSession session,
|
||||
string node,
|
||||
int vmid,
|
||||
string snapname)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.PostAsync($"nodes/{node}/lxc/{vmid}/snapshot/{snapname}/rollback")
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -239,6 +239,71 @@ namespace PSProxmoxVE.Core.Services
|
||||
client.DeleteAsync($"cluster/sdn/vnets/{vnet}").GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// SDN Subnets — requires PVE 8.0+
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns all subnets for an SDN VNet. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="vnet">The SDN VNet identifier.</param>
|
||||
public PveSdnSubnet[] GetSdnSubnets(PveSession session, string vnet)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveSdnSubnet[]>() ?? Array.Empty<PveSdnSubnet>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an SDN subnet on a VNet. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="vnet">The SDN VNet identifier.</param>
|
||||
/// <param name="config">Subnet configuration parameters including "subnet" (CIDR).</param>
|
||||
public void CreateSdnSubnet(
|
||||
PveSession session,
|
||||
string vnet,
|
||||
Dictionary<string, object> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var formData = config.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => kvp.Value?.ToString() ?? string.Empty);
|
||||
client.PostAsync($"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an SDN subnet from a VNet. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="vnet">The SDN VNet identifier.</param>
|
||||
/// <param name="subnet">The subnet CIDR to remove.</param>
|
||||
public void RemoveSdnSubnet(PveSession session, string vnet, string subnet)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
|
||||
if (string.IsNullOrWhiteSpace(subnet)) throw new ArgumentNullException(nameof(subnet));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync(
|
||||
$"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets/{Uri.EscapeDataString(subnet)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Management.Automation;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PSProxmoxVE.Core.Client;
|
||||
using PSProxmoxVE.Core.Models.Network;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists SDN subnets for a VNet in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Returns Software-Defined Networking subnet definitions for the specified VNet.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveSdnSubnet")]
|
||||
[OutputType(typeof(PveSdnSubnet))]
|
||||
public class GetPveSdnSubnetCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The SDN VNet to list subnets for.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN VNet name.")]
|
||||
public string Vnet { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Optional subnet CIDR filter.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Filter by subnet CIDR (e.g. 10.0.0.0/24).")]
|
||||
public string? Subnet { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
using var client = new PveHttpClient(session);
|
||||
|
||||
WriteVerbose($"Getting SDN subnets for VNet '{Vnet}'...");
|
||||
var json = client.GetAsync($"cluster/sdn/vnets/{System.Uri.EscapeDataString(Vnet)}/subnets")
|
||||
.GetAwaiter().GetResult();
|
||||
var root = JObject.Parse(json);
|
||||
var data = root["data"] as JArray ?? new JArray();
|
||||
|
||||
foreach (var item in data)
|
||||
{
|
||||
var subnet = item.ToObject<PveSdnSubnet>()!;
|
||||
|
||||
if (!string.IsNullOrEmpty(Subnet) &&
|
||||
!string.Equals(subnet.Subnet, Subnet, System.StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
WriteObject(subnet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Client;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Creates a new SDN subnet on a VNet in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Adds a new Software-Defined Networking subnet to the specified SDN VNet.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "PveSdnSubnet", SupportsShouldProcess = true)]
|
||||
public class NewPveSdnSubnetCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The SDN VNet to add the subnet to.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The SDN VNet name.")]
|
||||
public string Vnet { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The subnet CIDR notation (e.g. "10.0.0.0/24").</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The subnet in CIDR notation (e.g. 10.0.0.0/24).")]
|
||||
public string Subnet { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The gateway IP address for this subnet.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Gateway IP address for the subnet.")]
|
||||
public string? Gateway { get; set; }
|
||||
|
||||
/// <summary>Enable SNAT (source NAT) for this subnet.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Enable source NAT for this subnet.")]
|
||||
public SwitchParameter Snat { get; set; }
|
||||
|
||||
/// <summary>DNS zone prefix for this subnet.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "DNS zone prefix for this subnet.")]
|
||||
public string? DnsZonePrefix { get; set; }
|
||||
|
||||
/// <summary>DHCP range for automatic IP assignment (e.g. "start-address=10.0.0.100,end-address=10.0.0.200").</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "DHCP range for automatic IP assignment.")]
|
||||
public string? DhcpRange { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"Subnet {Subnet} on VNet {Vnet}", "Create PVE SDN Subnet"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
using var client = new PveHttpClient(session);
|
||||
|
||||
WriteVerbose($"Creating SDN subnet '{Subnet}' on VNet '{Vnet}'...");
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
["subnet"] = Subnet,
|
||||
["type"] = "subnet"
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(Gateway)) data["gateway"] = Gateway!;
|
||||
if (Snat.IsPresent) data["snat"] = "1";
|
||||
if (!string.IsNullOrEmpty(DnsZonePrefix)) data["dnszoneprefix"] = DnsZonePrefix!;
|
||||
if (!string.IsNullOrEmpty(DhcpRange)) data["dhcp-range"] = DhcpRange!;
|
||||
|
||||
client.PostAsync(
|
||||
$"cluster/sdn/vnets/{System.Uri.EscapeDataString(Vnet)}/subnets", data)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Client;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Removes an SDN subnet from a VNet in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Deletes the specified Software-Defined Networking subnet from the given VNet.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "PveSdnSubnet", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public class RemovePveSdnSubnetCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The SDN VNet containing the subnet.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The SDN VNet name.")]
|
||||
public string Vnet { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The subnet CIDR to remove (e.g. "10.0.0.0/24").</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The subnet CIDR to remove.")]
|
||||
public string Subnet { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"Subnet {Subnet} on VNet {Vnet}", "Remove PVE SDN Subnet"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
using var client = new PveHttpClient(session);
|
||||
|
||||
WriteVerbose($"Removing SDN subnet '{Subnet}' from VNet '{Vnet}'...");
|
||||
client.DeleteAsync(
|
||||
$"cluster/sdn/vnets/{System.Uri.EscapeDataString(Vnet)}/subnets/{System.Uri.EscapeDataString(Subnet)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,11 @@
|
||||
'Copy-PveContainer',
|
||||
'Get-PveContainerConfig',
|
||||
'Set-PveContainerConfig',
|
||||
# Container Snapshots (4)
|
||||
'Get-PveContainerSnapshot',
|
||||
'New-PveContainerSnapshot',
|
||||
'Remove-PveContainerSnapshot',
|
||||
'Restore-PveContainerSnapshot',
|
||||
|
||||
# Storage
|
||||
'Get-PveStorage',
|
||||
@@ -121,6 +126,10 @@
|
||||
'Get-PveSdnVnet',
|
||||
'New-PveSdnVnet',
|
||||
'Remove-PveSdnVnet',
|
||||
# SDN Subnets (3)
|
||||
'Get-PveSdnSubnet',
|
||||
'New-PveSdnSubnet',
|
||||
'Remove-PveSdnSubnet',
|
||||
|
||||
# Users
|
||||
'Get-PveUser',
|
||||
@@ -190,6 +199,9 @@
|
||||
# URI to the project for this module
|
||||
ProjectUri = 'https://github.com/goodolclint/PSProxmoxVE'
|
||||
|
||||
# Release notes for this version
|
||||
ReleaseNotes = 'Initial preview release. Supports PVE 8.x and 9.x with VM, container, storage, network, SDN, user/role/permission, template, cloud-init, snapshot, and task management.'
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for container snapshot cmdlets:
|
||||
Get-PveContainerSnapshot, New-PveContainerSnapshot,
|
||||
Remove-PveContainerSnapshot, Restore-PveContainerSnapshot.
|
||||
|
||||
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-PveContainerSnapshot', 'New-PveContainerSnapshot',
|
||||
'Remove-PveContainerSnapshot', 'Restore-PveContainerSnapshot')) {
|
||||
$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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Manifest contract
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Container snapshot cmdlets — manifest declarations' {
|
||||
BeforeAll {
|
||||
$manifestPath = Join-Path (Get-Module PSProxmoxVE).ModuleBase 'PSProxmoxVE.psd1'
|
||||
$script:Manifest = if (Test-Path $manifestPath) { Import-PowerShellDataFile $manifestPath } else { $null }
|
||||
}
|
||||
|
||||
It "<cmdName> should be declared in CmdletsToExport" -TestCases @(
|
||||
@{ cmdName = 'Get-PveContainerSnapshot' }
|
||||
@{ cmdName = 'New-PveContainerSnapshot' }
|
||||
@{ cmdName = 'Remove-PveContainerSnapshot' }
|
||||
@{ cmdName = 'Restore-PveContainerSnapshot' }
|
||||
) {
|
||||
if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return }
|
||||
$script:Manifest.CmdletsToExport | Should -Contain $cmdName
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveContainerSnapshot
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveContainerSnapshot' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveContainerSnapshot' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveContainerSnapshot'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveContainerSnapshot'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveContainerSnapshot'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveContainerSnapshot'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should have Name parameter (optional filter by snapshot name)' {
|
||||
Skip-IfMissing 'Get-PveContainerSnapshot'
|
||||
$script:Cmd.Parameters.ContainsKey('Name') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveContainerSnapshot'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveContainerSnapshot'
|
||||
{ Get-PveContainerSnapshot -Node 'pve-node1' -VmId 100 -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveContainerSnapshot
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveContainerSnapshot' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveContainerSnapshot' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveContainerSnapshot'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveContainerSnapshot'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Node should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveContainerSnapshot'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveContainerSnapshot'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Name (snapshot name) should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveContainerSnapshot'
|
||||
$isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Optional parameters' {
|
||||
It 'Should have Description parameter' {
|
||||
Skip-IfMissing 'New-PveContainerSnapshot'
|
||||
$script:Cmd.Parameters.ContainsKey('Description') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Wait switch parameter' {
|
||||
Skip-IfMissing 'New-PveContainerSnapshot'
|
||||
$script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should NOT have IncludeVmState (LXC containers do not support RAM snapshots)' {
|
||||
Skip-IfMissing 'New-PveContainerSnapshot'
|
||||
$script:Cmd.Parameters.ContainsKey('IncludeVmState') | Should -BeFalse
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active (without -WhatIf)' {
|
||||
Skip-IfMissing 'New-PveContainerSnapshot'
|
||||
{ New-PveContainerSnapshot -Node 'pve-node1' -VmId 100 -Name 'snap1' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveContainerSnapshot
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveContainerSnapshot' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveContainerSnapshot' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveContainerSnapshot'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveContainerSnapshot'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveContainerSnapshot'
|
||||
$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-PveContainerSnapshot'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveContainerSnapshot'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Name should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveContainerSnapshot'
|
||||
$isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active (without -WhatIf)' {
|
||||
Skip-IfMissing 'Remove-PveContainerSnapshot'
|
||||
{ Remove-PveContainerSnapshot -Node 'pve-node1' -VmId 100 -Name 'snap1' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Restore-PveContainerSnapshot
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Restore-PveContainerSnapshot' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Restore-PveContainerSnapshot' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Restore-PveContainerSnapshot'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Restore-PveContainerSnapshot'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Restore-PveContainerSnapshot'
|
||||
$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 'Restore-PveContainerSnapshot'
|
||||
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'VmId should be Mandatory' {
|
||||
Skip-IfMissing 'Restore-PveContainerSnapshot'
|
||||
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Name should be Mandatory' {
|
||||
Skip-IfMissing 'Restore-PveContainerSnapshot'
|
||||
$isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Optional parameters' {
|
||||
It 'Should have Wait switch parameter' {
|
||||
Skip-IfMissing 'Restore-PveContainerSnapshot'
|
||||
$script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active (without -WhatIf)' {
|
||||
Skip-IfMissing 'Restore-PveContainerSnapshot'
|
||||
{ Restore-PveContainerSnapshot -Node 'pve-node1' -VmId 100 -Name 'snap1' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for SDN subnet cmdlets:
|
||||
Get-PveSdnSubnet, New-PveSdnSubnet, Remove-PveSdnSubnet.
|
||||
|
||||
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-PveSdnSubnet', 'New-PveSdnSubnet', 'Remove-PveSdnSubnet')) {
|
||||
$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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Manifest contract
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'SDN subnet cmdlets — manifest declarations' {
|
||||
BeforeAll {
|
||||
$manifestPath = Join-Path (Get-Module PSProxmoxVE).ModuleBase 'PSProxmoxVE.psd1'
|
||||
$script:Manifest = if (Test-Path $manifestPath) { Import-PowerShellDataFile $manifestPath } else { $null }
|
||||
}
|
||||
|
||||
It "<cmdName> should be declared in CmdletsToExport" -TestCases @(
|
||||
@{ cmdName = 'Get-PveSdnSubnet' }
|
||||
@{ cmdName = 'New-PveSdnSubnet' }
|
||||
@{ cmdName = 'Remove-PveSdnSubnet' }
|
||||
) {
|
||||
if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return }
|
||||
$script:Manifest.CmdletsToExport | Should -Contain $cmdName
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveSdnSubnet
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveSdnSubnet' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveSdnSubnet' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveSdnSubnet'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveSdnSubnet'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Vnet should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveSdnSubnet'
|
||||
$isMandatory = $script:Cmd.Parameters['Vnet'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should have Subnet parameter (optional filter)' {
|
||||
Skip-IfMissing 'Get-PveSdnSubnet'
|
||||
$script:Cmd.Parameters.ContainsKey('Subnet') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveSdnSubnet'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Vnet should accept pipeline input by property name' {
|
||||
Skip-IfMissing 'Get-PveSdnSubnet'
|
||||
$attr = $script:Cmd.Parameters['Vnet'].ParameterSets.Values |
|
||||
Where-Object { $_.ValueFromPipelineByPropertyName }
|
||||
$attr | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveSdnSubnet'
|
||||
{ Get-PveSdnSubnet -Vnet 'myvnet' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveSdnSubnet
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveSdnSubnet' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveSdnSubnet' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveSdnSubnet'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveSdnSubnet'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Vnet should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveSdnSubnet'
|
||||
$isMandatory = $script:Cmd.Parameters['Vnet'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Subnet should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveSdnSubnet'
|
||||
$isMandatory = $script:Cmd.Parameters['Subnet'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Optional parameters' {
|
||||
It 'Should have Gateway parameter' {
|
||||
Skip-IfMissing 'New-PveSdnSubnet'
|
||||
$script:Cmd.Parameters.ContainsKey('Gateway') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Snat switch parameter' {
|
||||
Skip-IfMissing 'New-PveSdnSubnet'
|
||||
$script:Cmd.Parameters.ContainsKey('Snat') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have DnsZonePrefix parameter' {
|
||||
Skip-IfMissing 'New-PveSdnSubnet'
|
||||
$script:Cmd.Parameters.ContainsKey('DnsZonePrefix') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have DhcpRange parameter' {
|
||||
Skip-IfMissing 'New-PveSdnSubnet'
|
||||
$script:Cmd.Parameters.ContainsKey('DhcpRange') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active (without -WhatIf)' {
|
||||
Skip-IfMissing 'New-PveSdnSubnet'
|
||||
{ New-PveSdnSubnet -Vnet 'myvnet' -Subnet '10.0.0.0/24' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveSdnSubnet
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveSdnSubnet' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveSdnSubnet' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveSdnSubnet'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveSdnSubnet'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveSdnSubnet'
|
||||
$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 'Vnet should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveSdnSubnet'
|
||||
$isMandatory = $script:Cmd.Parameters['Vnet'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Subnet should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-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 (without -WhatIf)' {
|
||||
Skip-IfMissing 'Remove-PveSdnSubnet'
|
||||
{ Remove-PveSdnSubnet -Vnet 'myvnet' -Subnet '10.0.0.0/24' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user