mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-26 07:58:14 +00:00
feat: add firewall, backup, and SDN IPAM/DNS/controller cmdlets
Add 35 new cmdlets bringing the total to 118: - Firewall (21): rules, groups, aliases, IP sets, options at cluster/node/VM/container levels - Backup (5): ad-hoc vzdump and scheduled backup job CRUD - SDN IPAM/DNS/Controller (9): plugin management for SDN subsystem Also includes: - Fix: Remove-PveRole now has ConfirmImpact.High - Fix: URL-encode snapshot names in API paths - Refactor: extract auth header strings to constants in PveHttpClient - Add PSGallery version badge to README - Full test coverage: 11 JSON fixtures, xUnit model tests, Pester unit tests, and integration tests for firewall/backup/OVA import - Updated manifest, format file, CHANGELOG, README, API coverage docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,22 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Firewall management cmdlets (21): rules, security groups, aliases, IP sets, options at cluster/node/VM/container levels
|
||||
- Backup/vzdump cmdlets (5): ad-hoc backup creation and scheduled backup job CRUD
|
||||
- SDN IPAM cmdlets (3): Get/New/Remove-PveSdnIpam for IPAM plugin management
|
||||
- SDN DNS cmdlets (3): Get/New/Remove-PveSdnDns for DNS plugin management
|
||||
- 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
|
||||
|
||||
### Fixed
|
||||
|
||||
- `Remove-PveRole` now has `ConfirmImpact.High` for safety
|
||||
- URL-encode snapshot names in API paths for defense-in-depth
|
||||
- Extract auth header magic strings to named constants in PveHttpClient
|
||||
|
||||
## [0.1.0-preview] - 2026-03-19
|
||||
|
||||
### Added
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[](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)
|
||||
[](https://www.powershellgallery.com/packages/PSProxmoxVE)
|
||||
|
||||
A production-grade C# binary PowerShell module for managing Proxmox VE environments.
|
||||
|
||||
@@ -325,6 +326,53 @@ SDN management requires Proxmox VE 8.0 or later. Connected server is version 7.4
|
||||
| `Get-PveTask` | Get task status |
|
||||
| `Wait-PveTask` | Wait for a task to complete |
|
||||
|
||||
### Firewall
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `Get-PveFirewallRule` | List firewall rules (cluster/node/VM/container) |
|
||||
| `New-PveFirewallRule` | Create a firewall rule |
|
||||
| `Set-PveFirewallRule` | Update a firewall rule |
|
||||
| `Remove-PveFirewallRule` | Delete a firewall rule |
|
||||
| `Get-PveFirewallGroup` | List security groups or group rules |
|
||||
| `New-PveFirewallGroup` | Create a security group |
|
||||
| `Remove-PveFirewallGroup` | Delete a security group |
|
||||
| `Get-PveFirewallAlias` | List firewall IP aliases |
|
||||
| `New-PveFirewallAlias` | Create a firewall IP alias |
|
||||
| `Set-PveFirewallAlias` | Update a firewall IP alias |
|
||||
| `Remove-PveFirewallAlias` | Delete a firewall IP alias |
|
||||
| `Get-PveFirewallIpSet` | List firewall IP sets |
|
||||
| `New-PveFirewallIpSet` | Create a firewall IP set |
|
||||
| `Remove-PveFirewallIpSet` | Delete a firewall IP set |
|
||||
| `Get-PveFirewallIpSetEntry` | List entries in an IP set |
|
||||
| `New-PveFirewallIpSetEntry` | Add an entry to an IP set |
|
||||
| `Set-PveFirewallIpSetEntry` | Update an IP set entry |
|
||||
| `Remove-PveFirewallIpSetEntry` | Remove an entry from an IP set |
|
||||
| `Get-PveFirewallOptions` | Get firewall options |
|
||||
| `Set-PveFirewallOptions` | Set firewall options |
|
||||
| `Get-PveFirewallRef` | List firewall references (aliases, IP sets) |
|
||||
|
||||
### Backup
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `New-PveBackup` | Create an ad-hoc backup (vzdump) |
|
||||
| `Get-PveBackupJob` | List scheduled backup jobs |
|
||||
| `New-PveBackupJob` | Create a scheduled backup job |
|
||||
| `Set-PveBackupJob` | Update a scheduled backup job |
|
||||
| `Remove-PveBackupJob` | Delete a scheduled backup job |
|
||||
|
||||
### SDN — IPAM / DNS / Controllers (PVE 8.0+)
|
||||
| Cmdlet | Description |
|
||||
|---|---|
|
||||
| `Get-PveSdnIpam` | List SDN IPAM plugins |
|
||||
| `New-PveSdnIpam` | Create an SDN IPAM plugin |
|
||||
| `Remove-PveSdnIpam` | Remove an SDN IPAM plugin |
|
||||
| `Get-PveSdnDns` | List SDN DNS plugins |
|
||||
| `New-PveSdnDns` | Create an SDN DNS plugin |
|
||||
| `Remove-PveSdnDns` | Remove an SDN DNS plugin |
|
||||
| `Get-PveSdnController` | List SDN controllers |
|
||||
| `New-PveSdnController` | Create an SDN controller |
|
||||
| `Remove-PveSdnController` | Remove an SDN controller |
|
||||
|
||||
## Known Limitations (v1)
|
||||
|
||||
- **No automatic retries**: Failed API calls are not retried. Implement your own retry logic if needed.
|
||||
|
||||
+583
-543
File diff suppressed because it is too large
Load Diff
+10
-12
@@ -2,9 +2,9 @@
|
||||
|
||||
This document tracks which PVE API areas are implemented in PSProxmoxVE and which are planned for future releases.
|
||||
|
||||
**Last updated:** 2026-03-20
|
||||
**Last updated:** 2026-03-21
|
||||
**Module version:** 0.1.0-preview
|
||||
**Total cmdlets:** 75
|
||||
**Total cmdlets:** 110
|
||||
|
||||
## Implemented
|
||||
|
||||
@@ -28,17 +28,18 @@ This document tracks which PVE API areas are implemented in PSProxmoxVE and whic
|
||||
| **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) |
|
||||
| **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) |
|
||||
|
||||
## Not Yet Implemented
|
||||
|
||||
### High-Value Gaps
|
||||
### Gaps
|
||||
|
||||
These are the most impactful areas for real-world automation that are not yet covered.
|
||||
|
||||
| Area | Key Endpoints | Use Case | Priority |
|
||||
|------|--------------|----------|----------|
|
||||
| **Firewall** | `/cluster/firewall/*`, `/nodes/{node}/firewall/*`, `/nodes/{node}/qemu/{vmid}/firewall/*` | Security automation, rule management, IP sets, aliases | High |
|
||||
| **Backup / vzdump** | `/nodes/{node}/vzdump`, `/cluster/backup/*` | Disaster recovery, scheduled backups, backup job management | High |
|
||||
| Area | Key Endpoints | Notes | Priority |
|
||||
|------|--------------|-------|----------|
|
||||
| **Pool Management** | `/pools/*` | Multi-tenant environments, resource grouping | Medium |
|
||||
|
||||
### Lower Priority Gaps
|
||||
@@ -56,9 +57,6 @@ These are the most impactful areas for real-world automation that are not yet co
|
||||
| **Node Management** | `/nodes/{node}/apt/*`, `/nodes/{node}/disks/*`, `/nodes/{node}/services/*` | Package updates, disk management, service control |
|
||||
| **Metrics** | `/cluster/metrics/*` | External metrics server configuration |
|
||||
| **Additional VM Agent** | `/nodes/{node}/qemu/{vmid}/agent/*` | File read/write, OS info, suspend/resume via agent |
|
||||
| **SDN IPAM** | `/cluster/sdn/ipams/*` | IP address management for SDN |
|
||||
| **SDN DNS** | `/cluster/sdn/dns/*` | DNS integration for SDN |
|
||||
| **SDN Controllers** | `/cluster/sdn/controllers/*` | SDN controller configuration |
|
||||
|
||||
## Contributing New Cmdlets
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ namespace PSProxmoxVE.Core.Client
|
||||
private readonly HttpClient _httpClient;
|
||||
private bool _disposed;
|
||||
|
||||
private const string ApiTokenPrefix = "PVEAPIToken=";
|
||||
private const string AuthCookieName = "PVEAuthCookie=";
|
||||
private const string CsrfHeaderName = "CSRFPreventionToken";
|
||||
|
||||
/// <summary>
|
||||
/// Creates an HTTP client authenticated with the specified PVE session.
|
||||
/// </summary>
|
||||
@@ -313,14 +317,14 @@ namespace PSProxmoxVE.Core.Client
|
||||
|
||||
if (_session.AuthMode == PveAuthMode.ApiToken)
|
||||
{
|
||||
request.Headers.TryAddWithoutValidation("Authorization", $"PVEAPIToken={_session.ApiToken}");
|
||||
request.Headers.TryAddWithoutValidation("Authorization", $"{ApiTokenPrefix}{_session.ApiToken}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ticket auth
|
||||
request.Headers.Add("Cookie", $"PVEAuthCookie={_session.Ticket}");
|
||||
request.Headers.Add("Cookie", $"{AuthCookieName}{_session.Ticket}");
|
||||
if (mutating && !string.IsNullOrEmpty(_session.CsrfToken))
|
||||
request.Headers.Add("CSRFPreventionToken", _session.CsrfToken);
|
||||
request.Headers.Add(CsrfHeaderName, _session.CsrfToken);
|
||||
}
|
||||
|
||||
return request;
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Backup;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Proxmox VE scheduled backup job as returned by
|
||||
/// the /cluster/backup endpoint.
|
||||
/// </summary>
|
||||
public class PveBackupJob
|
||||
{
|
||||
/// <summary>
|
||||
/// The backup job identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The job type (typically "vzdump").
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the job is enabled (1) or disabled (0).
|
||||
/// </summary>
|
||||
[JsonPropertyName("enabled")]
|
||||
[JsonProperty("enabled")]
|
||||
public int? Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The cron-style schedule (e.g. "0 2 * * *").
|
||||
/// </summary>
|
||||
[JsonPropertyName("schedule")]
|
||||
[JsonProperty("schedule")]
|
||||
public string? Schedule { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The target storage for backup files.
|
||||
/// </summary>
|
||||
[JsonPropertyName("storage")]
|
||||
[JsonProperty("storage")]
|
||||
public string? Storage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Backup mode: "snapshot", "suspend", or "stop".
|
||||
/// </summary>
|
||||
[JsonPropertyName("mode")]
|
||||
[JsonProperty("mode")]
|
||||
public string? Mode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Comma-separated list of VM/container IDs to back up, or empty for all.
|
||||
/// </summary>
|
||||
[JsonPropertyName("vmid")]
|
||||
[JsonProperty("vmid")]
|
||||
public string? VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to back up all VMs/containers (1 = all).
|
||||
/// </summary>
|
||||
[JsonPropertyName("all")]
|
||||
[JsonProperty("all")]
|
||||
public int? All { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Compression algorithm (e.g. "zstd", "lzo", "gzip").
|
||||
/// </summary>
|
||||
[JsonPropertyName("compress")]
|
||||
[JsonProperty("compress")]
|
||||
public string? Compress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated — maximum number of backup files to keep. Use prune-backups instead.
|
||||
/// </summary>
|
||||
[JsonPropertyName("maxfiles")]
|
||||
[JsonProperty("maxfiles")]
|
||||
public int? MaxFiles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Retention policy string (e.g. "keep-daily=7,keep-weekly=4").
|
||||
/// </summary>
|
||||
[JsonPropertyName("prune-backups")]
|
||||
[JsonProperty("prune-backups")]
|
||||
public string? PruneBackups { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Template for backup notes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("notes-template")]
|
||||
[JsonProperty("notes-template")]
|
||||
public string? NotesTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional comment describing the backup job.
|
||||
/// </summary>
|
||||
[JsonPropertyName("comment")]
|
||||
[JsonProperty("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Mail notification setting (e.g. "always", "failure").
|
||||
/// </summary>
|
||||
[JsonPropertyName("mailnotification")]
|
||||
[JsonProperty("mailnotification")]
|
||||
public string? MailNotification { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Email address to send notifications to.
|
||||
/// </summary>
|
||||
[JsonPropertyName("mailto")]
|
||||
[JsonProperty("mailto")]
|
||||
public string? MailTo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specific node to run the backup on (empty = any node).
|
||||
/// </summary>
|
||||
[JsonPropertyName("node")]
|
||||
[JsonProperty("node")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Comma-separated list of excluded VM/container IDs.
|
||||
/// </summary>
|
||||
[JsonPropertyName("exclude")]
|
||||
[JsonProperty("exclude")]
|
||||
public string? Exclude { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var state = Enabled == 1 ? "Enabled" : "Disabled";
|
||||
return $"Backup Job: {Id} | {state} | Schedule: {Schedule ?? "N/A"} | "
|
||||
+ $"Storage: {Storage ?? "N/A"} | Mode: {Mode ?? "N/A"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Firewall;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Proxmox VE firewall IP alias as returned by
|
||||
/// the firewall/aliases endpoints.
|
||||
/// </summary>
|
||||
public class PveFirewallAlias
|
||||
{
|
||||
/// <summary>
|
||||
/// The alias name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The CIDR network or IP address (e.g. "10.0.0.0/8" or "192.168.1.1").
|
||||
/// </summary>
|
||||
[JsonPropertyName("cidr")]
|
||||
[JsonProperty("cidr")]
|
||||
public string Cidr { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Optional comment describing the alias.
|
||||
/// </summary>
|
||||
[JsonPropertyName("comment")]
|
||||
[JsonProperty("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Alias: {Name} → {Cidr}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Firewall;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Proxmox VE firewall security group as returned by
|
||||
/// the /cluster/firewall/groups endpoint.
|
||||
/// </summary>
|
||||
public class PveFirewallGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// The security group name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("group")]
|
||||
[JsonProperty("group")]
|
||||
public string Group { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Optional comment describing the group.
|
||||
/// </summary>
|
||||
[JsonPropertyName("comment")]
|
||||
[JsonProperty("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Configuration digest for detecting concurrent modifications.
|
||||
/// </summary>
|
||||
[JsonPropertyName("digest")]
|
||||
[JsonProperty("digest")]
|
||||
public string? Digest { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Firewall Group: {Group} | Comment: {Comment ?? "N/A"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Firewall;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Proxmox VE firewall IP set as returned by
|
||||
/// the firewall/ipset endpoints.
|
||||
/// </summary>
|
||||
public class PveFirewallIpSet
|
||||
{
|
||||
/// <summary>
|
||||
/// The IP set name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Optional comment describing the IP set.
|
||||
/// </summary>
|
||||
[JsonPropertyName("comment")]
|
||||
[JsonProperty("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Configuration digest for detecting concurrent modifications.
|
||||
/// </summary>
|
||||
[JsonPropertyName("digest")]
|
||||
[JsonProperty("digest")]
|
||||
public string? Digest { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"IP Set: {Name} | Comment: {Comment ?? "N/A"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Firewall;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an entry within a Proxmox VE firewall IP set as returned by
|
||||
/// the firewall/ipset/{name} endpoints.
|
||||
/// </summary>
|
||||
public class PveFirewallIpSetEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The CIDR network or IP address (e.g. "192.168.1.0/24" or "10.0.0.1").
|
||||
/// </summary>
|
||||
[JsonPropertyName("cidr")]
|
||||
[JsonProperty("cidr")]
|
||||
public string Cidr { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is an exclusion entry (1 = exclude from set).
|
||||
/// </summary>
|
||||
[JsonPropertyName("nomatch")]
|
||||
[JsonProperty("nomatch")]
|
||||
public int? NoMatch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional comment describing the entry.
|
||||
/// </summary>
|
||||
[JsonPropertyName("comment")]
|
||||
[JsonProperty("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var prefix = NoMatch == 1 ? "!" : "";
|
||||
return $"{prefix}{Cidr}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Firewall;
|
||||
|
||||
/// <summary>
|
||||
/// Represents Proxmox VE firewall options as returned by
|
||||
/// the firewall/options endpoints at cluster, node, VM, or container level.
|
||||
/// </summary>
|
||||
public class PveFirewallOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the firewall is enabled (1) or disabled (0).
|
||||
/// </summary>
|
||||
[JsonPropertyName("enable")]
|
||||
[JsonProperty("enable")]
|
||||
public int? Enable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default policy for incoming traffic (ACCEPT, DROP, REJECT).
|
||||
/// </summary>
|
||||
[JsonPropertyName("policy_in")]
|
||||
[JsonProperty("policy_in")]
|
||||
public string? PolicyIn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default policy for outgoing traffic (ACCEPT, DROP, REJECT).
|
||||
/// </summary>
|
||||
[JsonPropertyName("policy_out")]
|
||||
[JsonProperty("policy_out")]
|
||||
public string? PolicyOut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Log level for incoming traffic (nolog, emerg, alert, crit, err, warning, notice, info, debug).
|
||||
/// </summary>
|
||||
[JsonPropertyName("log_level_in")]
|
||||
[JsonProperty("log_level_in")]
|
||||
public string? LogLevelIn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Log level for outgoing traffic.
|
||||
/// </summary>
|
||||
[JsonPropertyName("log_level_out")]
|
||||
[JsonProperty("log_level_out")]
|
||||
public string? LogLevelOut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to allow DHCP traffic (VM/container level).
|
||||
/// </summary>
|
||||
[JsonPropertyName("dhcp")]
|
||||
[JsonProperty("dhcp")]
|
||||
public int? Dhcp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to allow NDP (IPv6 Neighbor Discovery Protocol).
|
||||
/// </summary>
|
||||
[JsonPropertyName("ndp")]
|
||||
[JsonProperty("ndp")]
|
||||
public int? Ndp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to allow Router Advertisement.
|
||||
/// </summary>
|
||||
[JsonPropertyName("radv")]
|
||||
[JsonProperty("radv")]
|
||||
public int? Radv { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to enable MAC address filter (VM/container level).
|
||||
/// </summary>
|
||||
[JsonPropertyName("macfilter")]
|
||||
[JsonProperty("macfilter")]
|
||||
public int? MacFilter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to enable IP filter (VM/container level).
|
||||
/// </summary>
|
||||
[JsonPropertyName("ipfilter")]
|
||||
[JsonProperty("ipfilter")]
|
||||
public int? IpFilter { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var state = Enable == 1 ? "Enabled" : "Disabled";
|
||||
return $"Firewall: {state} | In: {PolicyIn ?? "N/A"} | Out: {PolicyOut ?? "N/A"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Firewall;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Proxmox VE firewall reference item (alias, ipset, etc.)
|
||||
/// as returned by the firewall/refs endpoints.
|
||||
/// </summary>
|
||||
public class PveFirewallRef
|
||||
{
|
||||
/// <summary>
|
||||
/// The reference type (e.g. "alias", "ipset").
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The reference name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Optional comment.
|
||||
/// </summary>
|
||||
[JsonPropertyName("comment")]
|
||||
[JsonProperty("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Type}: {Name}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Firewall;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Proxmox VE firewall rule as returned by
|
||||
/// the firewall/rules endpoints at cluster, node, VM, or container level.
|
||||
/// </summary>
|
||||
public class PveFirewallRule
|
||||
{
|
||||
/// <summary>
|
||||
/// Rule position (used for ordering and as identifier for updates/deletes).
|
||||
/// </summary>
|
||||
[JsonPropertyName("pos")]
|
||||
[JsonProperty("pos")]
|
||||
public int? Pos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rule type: "in", "out", or "group".
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Action to take: ACCEPT, DROP, or REJECT.
|
||||
/// </summary>
|
||||
[JsonPropertyName("action")]
|
||||
[JsonProperty("action")]
|
||||
public string Action { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the rule is enabled (1) or disabled (0).
|
||||
/// </summary>
|
||||
[JsonPropertyName("enable")]
|
||||
[JsonProperty("enable")]
|
||||
public int? Enable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Source address or alias.
|
||||
/// </summary>
|
||||
[JsonPropertyName("source")]
|
||||
[JsonProperty("source")]
|
||||
public string? Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Destination address or alias.
|
||||
/// </summary>
|
||||
[JsonPropertyName("dest")]
|
||||
[JsonProperty("dest")]
|
||||
public string? Dest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Protocol (e.g. tcp, udp, icmp).
|
||||
/// </summary>
|
||||
[JsonPropertyName("proto")]
|
||||
[JsonProperty("proto")]
|
||||
public string? Proto { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Destination port or port range.
|
||||
/// </summary>
|
||||
[JsonPropertyName("dport")]
|
||||
[JsonProperty("dport")]
|
||||
public string? Dport { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Source port or port range.
|
||||
/// </summary>
|
||||
[JsonPropertyName("sport")]
|
||||
[JsonProperty("sport")]
|
||||
public string? Sport { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional comment describing the rule.
|
||||
/// </summary>
|
||||
[JsonPropertyName("comment")]
|
||||
[JsonProperty("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Predefined macro name (e.g. "SSH", "HTTP", "DNS").
|
||||
/// </summary>
|
||||
[JsonPropertyName("macro")]
|
||||
[JsonProperty("macro")]
|
||||
public string? Macro { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Log level for matched packets (e.g. "nolog", "info", "warning").
|
||||
/// </summary>
|
||||
[JsonPropertyName("log")]
|
||||
[JsonProperty("log")]
|
||||
public string? Log { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Network interface to match (e.g. "net0").
|
||||
/// </summary>
|
||||
[JsonPropertyName("iface")]
|
||||
[JsonProperty("iface")]
|
||||
public string? Iface { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var action = Macro != null ? $"{Action} ({Macro})" : Action;
|
||||
return $"Rule {Pos}: {Type} {action} | "
|
||||
+ $"Src: {Source ?? "any"} → Dst: {Dest ?? "any"} | "
|
||||
+ $"Proto: {Proto ?? "any"} DPort: {Dport ?? "any"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Network;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Software-Defined Networking (SDN) controller as returned by
|
||||
/// the /cluster/sdn/controllers endpoint. Requires Proxmox VE 8.0+.
|
||||
/// </summary>
|
||||
public class PveSdnController
|
||||
{
|
||||
/// <summary>
|
||||
/// The controller identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("controller")]
|
||||
[JsonProperty("controller")]
|
||||
public string Controller { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The controller type (e.g. "evpn", "bgp").
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The Autonomous System Number for BGP/EVPN.
|
||||
/// </summary>
|
||||
[JsonPropertyName("asn")]
|
||||
[JsonProperty("asn")]
|
||||
public int? Asn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Comma-separated list of BGP peer addresses.
|
||||
/// </summary>
|
||||
[JsonPropertyName("peers")]
|
||||
[JsonProperty("peers")]
|
||||
public string? Peers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The node this controller is configured on.
|
||||
/// </summary>
|
||||
[JsonPropertyName("node")]
|
||||
[JsonProperty("node")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"SDN Controller: {Controller} | Type: {Type} | ASN: {Asn?.ToString() ?? "N/A"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Network;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Software-Defined Networking (SDN) DNS plugin as returned by
|
||||
/// the /cluster/sdn/dns endpoint. Requires Proxmox VE 8.0+.
|
||||
/// </summary>
|
||||
public class PveSdnDns
|
||||
{
|
||||
/// <summary>
|
||||
/// The DNS plugin identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("dns")]
|
||||
[JsonProperty("dns")]
|
||||
public string Dns { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The DNS plugin type (e.g. "powerdns").
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the DNS service API.
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
[JsonProperty("url")]
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The API key for the DNS service.
|
||||
/// </summary>
|
||||
[JsonPropertyName("key")]
|
||||
[JsonProperty("key")]
|
||||
public string? Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The IPv6 reverse zone mask length.
|
||||
/// </summary>
|
||||
[JsonPropertyName("reversemaskv6")]
|
||||
[JsonProperty("reversemaskv6")]
|
||||
public int? ReverseMaskV6 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The TTL (time-to-live) for DNS records.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ttl")]
|
||||
[JsonProperty("ttl")]
|
||||
public int? Ttl { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"SDN DNS: {Dns} | Type: {Type} | URL: {Url ?? "N/A"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Network;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Software-Defined Networking (SDN) IPAM plugin as returned by
|
||||
/// the /cluster/sdn/ipams endpoint. Requires Proxmox VE 8.0+.
|
||||
/// </summary>
|
||||
public class PveSdnIpam
|
||||
{
|
||||
/// <summary>
|
||||
/// The IPAM plugin identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ipam")]
|
||||
[JsonProperty("ipam")]
|
||||
public string Ipam { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The IPAM type (e.g. "pve", "netbox", "phpipam").
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the external IPAM service (for netbox/phpipam types).
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
[JsonProperty("url")]
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The API token for the external IPAM service.
|
||||
/// </summary>
|
||||
[JsonPropertyName("token")]
|
||||
[JsonProperty("token")]
|
||||
public string? Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The configuration section identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("section")]
|
||||
[JsonProperty("section")]
|
||||
public int? Section { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"SDN IPAM: {Ipam} | Type: {Type}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PSProxmoxVE.Core.Authentication;
|
||||
using PSProxmoxVE.Core.Client;
|
||||
using PSProxmoxVE.Core.Models.Backup;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
|
||||
namespace PSProxmoxVE.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for Proxmox VE Backup (vzdump) and backup job API operations.
|
||||
/// </summary>
|
||||
public class BackupService
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// Ad-hoc backup (vzdump)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Creates an ad-hoc backup via vzdump. Returns the task UPID.
|
||||
/// </summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The node to run the backup on.</param>
|
||||
/// <param name="config">Backup configuration parameters.</param>
|
||||
public PveTask CreateBackup(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);
|
||||
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/vzdump", config)
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Backup jobs (scheduled)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns all scheduled backup jobs.
|
||||
/// </summary>
|
||||
public PveBackupJob[] GetBackupJobs(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync("cluster/backup").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveBackupJob[]>() ?? Array.Empty<PveBackupJob>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a single scheduled backup job by ID.
|
||||
/// </summary>
|
||||
public PveBackupJob? GetBackupJob(PveSession session, string id)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"cluster/backup/{Uri.EscapeDataString(id)}")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveBackupJob>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scheduled backup job.
|
||||
/// </summary>
|
||||
public void CreateBackupJob(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("cluster/backup", config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a scheduled backup job.
|
||||
/// </summary>
|
||||
public void UpdateBackupJob(PveSession session, string id, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"cluster/backup/{Uri.EscapeDataString(id)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a scheduled backup job.
|
||||
/// </summary>
|
||||
public void RemoveBackupJob(PveSession session, string id)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"cluster/backup/{Uri.EscapeDataString(id)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,7 +173,7 @@ namespace PSProxmoxVE.Core.Services
|
||||
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}")
|
||||
var response = client.DeleteAsync($"nodes/{node}/lxc/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}")
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
@@ -192,7 +192,7 @@ namespace PSProxmoxVE.Core.Services
|
||||
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")
|
||||
var response = client.PostAsync($"nodes/{node}/lxc/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}/rollback")
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PSProxmoxVE.Core.Authentication;
|
||||
using PSProxmoxVE.Core.Client;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
|
||||
namespace PSProxmoxVE.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for Proxmox VE Firewall API operations.
|
||||
/// Supports firewall management at cluster, node, VM, and container levels.
|
||||
/// </summary>
|
||||
public class FirewallService
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// Rules
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns firewall rules at the specified level.
|
||||
/// </summary>
|
||||
public PveFirewallRule[] GetRules(PveSession session, string level, string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"{basePath}/rules").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveFirewallRule[]>() ?? Array.Empty<PveFirewallRule>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a firewall rule at the specified level.
|
||||
/// </summary>
|
||||
public void CreateRule(PveSession session, string level, Dictionary<string, string> config,
|
||||
string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync($"{basePath}/rules", config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a firewall rule at the specified position.
|
||||
/// </summary>
|
||||
public void UpdateRule(PveSession session, string level, int pos, Dictionary<string, string> config,
|
||||
string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"{basePath}/rules/{pos}", config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a firewall rule at the specified position.
|
||||
/// </summary>
|
||||
public void RemoveRule(PveSession session, string level, int pos,
|
||||
string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"{basePath}/rules/{pos}").GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Security Groups (cluster level only)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns all firewall security groups.
|
||||
/// </summary>
|
||||
public PveFirewallGroup[] GetGroups(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync("cluster/firewall/groups").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveFirewallGroup[]>() ?? Array.Empty<PveFirewallGroup>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a firewall security group.
|
||||
/// </summary>
|
||||
public void CreateGroup(PveSession session, string name, string? comment = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
|
||||
|
||||
var formData = new Dictionary<string, string> { ["group"] = name };
|
||||
if (!string.IsNullOrEmpty(comment))
|
||||
formData["comment"] = comment!;
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync("cluster/firewall/groups", formData).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a firewall security group.
|
||||
/// </summary>
|
||||
public void RemoveGroup(PveSession session, string name)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"cluster/firewall/groups/{Uri.EscapeDataString(name)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns firewall rules within a security group.
|
||||
/// </summary>
|
||||
public PveFirewallRule[] GetGroupRules(PveSession session, string group)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveFirewallRule[]>() ?? Array.Empty<PveFirewallRule>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rule within a security group.
|
||||
/// </summary>
|
||||
public void CreateGroupRule(PveSession session, string group, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a rule within a security group.
|
||||
/// </summary>
|
||||
public void UpdateGroupRule(PveSession session, string group, int pos, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}/{pos}", config)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a rule from a security group.
|
||||
/// </summary>
|
||||
public void RemoveGroupRule(PveSession session, string group, int pos)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}/{pos}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Aliases
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns firewall aliases at the specified level.
|
||||
/// </summary>
|
||||
public PveFirewallAlias[] GetAliases(PveSession session, string level, string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"{basePath}/aliases").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveFirewallAlias[]>() ?? Array.Empty<PveFirewallAlias>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a firewall alias.
|
||||
/// </summary>
|
||||
public void CreateAlias(PveSession session, string level, string name, string cidr,
|
||||
string? comment = null, string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
|
||||
if (string.IsNullOrWhiteSpace(cidr)) throw new ArgumentNullException(nameof(cidr));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
var formData = new Dictionary<string, string>
|
||||
{
|
||||
["name"] = name,
|
||||
["cidr"] = cidr
|
||||
};
|
||||
if (!string.IsNullOrEmpty(comment))
|
||||
formData["comment"] = comment!;
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync($"{basePath}/aliases", formData).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a firewall alias.
|
||||
/// </summary>
|
||||
public void UpdateAlias(PveSession session, string level, string name,
|
||||
string? cidr = null, string? comment = null, string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
var formData = new Dictionary<string, string>();
|
||||
if (!string.IsNullOrEmpty(cidr))
|
||||
formData["cidr"] = cidr!;
|
||||
if (!string.IsNullOrEmpty(comment))
|
||||
formData["comment"] = comment!;
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"{basePath}/aliases/{Uri.EscapeDataString(name)}", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a firewall alias.
|
||||
/// </summary>
|
||||
public void RemoveAlias(PveSession session, string level, string name,
|
||||
string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"{basePath}/aliases/{Uri.EscapeDataString(name)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// IP Sets
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns firewall IP sets at the specified level.
|
||||
/// </summary>
|
||||
public PveFirewallIpSet[] GetIpSets(PveSession session, string level, string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"{basePath}/ipset").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveFirewallIpSet[]>() ?? Array.Empty<PveFirewallIpSet>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a firewall IP set.
|
||||
/// </summary>
|
||||
public void CreateIpSet(PveSession session, string level, string name,
|
||||
string? comment = null, string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
var formData = new Dictionary<string, string> { ["name"] = name };
|
||||
if (!string.IsNullOrEmpty(comment))
|
||||
formData["comment"] = comment!;
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync($"{basePath}/ipset", formData).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a firewall IP set.
|
||||
/// </summary>
|
||||
public void RemoveIpSet(PveSession session, string level, string name,
|
||||
string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"{basePath}/ipset/{Uri.EscapeDataString(name)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// IP Set Entries
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns entries within an IP set.
|
||||
/// </summary>
|
||||
public PveFirewallIpSetEntry[] GetIpSetEntries(PveSession session, string level, string name,
|
||||
string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"{basePath}/ipset/{Uri.EscapeDataString(name)}")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveFirewallIpSetEntry[]>() ?? Array.Empty<PveFirewallIpSetEntry>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an entry to an IP set.
|
||||
/// </summary>
|
||||
public void AddIpSetEntry(PveSession session, string level, string name, string cidr,
|
||||
bool nomatch = false, string? comment = null, string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
|
||||
if (string.IsNullOrWhiteSpace(cidr)) throw new ArgumentNullException(nameof(cidr));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
var formData = new Dictionary<string, string> { ["cidr"] = cidr };
|
||||
if (nomatch)
|
||||
formData["nomatch"] = "1";
|
||||
if (!string.IsNullOrEmpty(comment))
|
||||
formData["comment"] = comment!;
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync($"{basePath}/ipset/{Uri.EscapeDataString(name)}", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an entry in an IP set.
|
||||
/// </summary>
|
||||
public void UpdateIpSetEntry(PveSession session, string level, string name, string cidr,
|
||||
bool? nomatch = null, string? comment = null, string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
|
||||
if (string.IsNullOrWhiteSpace(cidr)) throw new ArgumentNullException(nameof(cidr));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
var formData = new Dictionary<string, string>();
|
||||
if (nomatch.HasValue)
|
||||
formData["nomatch"] = nomatch.Value ? "1" : "0";
|
||||
if (!string.IsNullOrEmpty(comment))
|
||||
formData["comment"] = comment!;
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync(
|
||||
$"{basePath}/ipset/{Uri.EscapeDataString(name)}/{Uri.EscapeDataString(cidr)}",
|
||||
formData).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an entry from an IP set.
|
||||
/// </summary>
|
||||
public void RemoveIpSetEntry(PveSession session, string level, string name, string cidr,
|
||||
string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
|
||||
if (string.IsNullOrWhiteSpace(cidr)) throw new ArgumentNullException(nameof(cidr));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync(
|
||||
$"{basePath}/ipset/{Uri.EscapeDataString(name)}/{Uri.EscapeDataString(cidr)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Options
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns firewall options at the specified level.
|
||||
/// </summary>
|
||||
public PveFirewallOptions GetOptions(PveSession session, string level,
|
||||
string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync($"{basePath}/options").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveFirewallOptions>() ?? new PveFirewallOptions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets firewall options at the specified level.
|
||||
/// </summary>
|
||||
public void SetOptions(PveSession session, string level, Dictionary<string, string> config,
|
||||
string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PutAsync($"{basePath}/options", config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Refs
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns firewall references (aliases, IP sets) at the specified level.
|
||||
/// </summary>
|
||||
public PveFirewallRef[] GetRefs(PveSession session, string level, string? type = null,
|
||||
string? node = null, int? vmid = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
var basePath = BuildBasePath(level, node, vmid);
|
||||
|
||||
var resource = $"{basePath}/refs";
|
||||
if (!string.IsNullOrEmpty(type))
|
||||
resource += $"?type={Uri.EscapeDataString(type!)}";
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync(resource).GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveFirewallRef[]>() ?? Array.Empty<PveFirewallRef>();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Builds the API base path for the specified firewall level.
|
||||
/// </summary>
|
||||
private static string BuildBasePath(string level, string? node, int? vmid)
|
||||
{
|
||||
switch (level.ToLowerInvariant())
|
||||
{
|
||||
case "cluster":
|
||||
return "cluster/firewall";
|
||||
case "node":
|
||||
if (string.IsNullOrWhiteSpace(node))
|
||||
throw new ArgumentException("Node is required for node-level firewall operations.", nameof(node));
|
||||
return $"nodes/{Uri.EscapeDataString(node)}/firewall";
|
||||
case "vm":
|
||||
if (string.IsNullOrWhiteSpace(node))
|
||||
throw new ArgumentException("Node is required for VM-level firewall operations.", nameof(node));
|
||||
if (!vmid.HasValue)
|
||||
throw new ArgumentException("VmId is required for VM-level firewall operations.", nameof(vmid));
|
||||
return $"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid.Value}/firewall";
|
||||
case "container":
|
||||
if (string.IsNullOrWhiteSpace(node))
|
||||
throw new ArgumentException("Node is required for container-level firewall operations.", nameof(node));
|
||||
if (!vmid.HasValue)
|
||||
throw new ArgumentException("VmId is required for container-level firewall operations.", nameof(vmid));
|
||||
return $"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid.Value}/firewall";
|
||||
default:
|
||||
throw new ArgumentException($"Invalid firewall level: {level}. Must be Cluster, Node, Vm, or Container.", nameof(level));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,6 +304,141 @@ namespace PSProxmoxVE.Core.Services
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// SDN IPAM
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns all SDN IPAM plugins. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
public PveSdnIpam[] GetSdnIpams(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync("cluster/sdn/ipams").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveSdnIpam[]>() ?? Array.Empty<PveSdnIpam>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an SDN IPAM plugin. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
public void CreateSdnIpam(PveSession session, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync("cluster/sdn/ipams", config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an SDN IPAM plugin. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
public void RemoveSdnIpam(PveSession session, string ipam)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
if (string.IsNullOrWhiteSpace(ipam)) throw new ArgumentNullException(nameof(ipam));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"cluster/sdn/ipams/{Uri.EscapeDataString(ipam)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// SDN DNS
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns all SDN DNS plugins. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
public PveSdnDns[] GetSdnDnsPlugins(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync("cluster/sdn/dns").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveSdnDns[]>() ?? Array.Empty<PveSdnDns>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an SDN DNS plugin. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
public void CreateSdnDnsPlugin(PveSession session, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync("cluster/sdn/dns", config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an SDN DNS plugin. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
public void RemoveSdnDnsPlugin(PveSession session, string dns)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
if (string.IsNullOrWhiteSpace(dns)) throw new ArgumentNullException(nameof(dns));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"cluster/sdn/dns/{Uri.EscapeDataString(dns)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// SDN Controllers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns all SDN controllers. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
public PveSdnController[] GetSdnControllers(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.GetAsync("cluster/sdn/controllers").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveSdnController[]>() ?? Array.Empty<PveSdnController>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an SDN controller. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
public void CreateSdnController(PveSession session, Dictionary<string, string> config)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.PostAsync("cluster/sdn/controllers", config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an SDN controller. Requires PVE 8.0+.
|
||||
/// </summary>
|
||||
public void RemoveSdnController(PveSession session, string controller)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
RequireSdn(session);
|
||||
if (string.IsNullOrWhiteSpace(controller)) throw new ArgumentNullException(nameof(controller));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
client.DeleteAsync($"cluster/sdn/controllers/{Uri.EscapeDataString(controller)}")
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace PSProxmoxVE.Core.Services
|
||||
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.DeleteAsync($"nodes/{node}/qemu/{vmid}/snapshot/{snapname}")
|
||||
var response = client.DeleteAsync($"nodes/{node}/qemu/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}")
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
@@ -107,7 +107,7 @@ namespace PSProxmoxVE.Core.Services
|
||||
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
|
||||
|
||||
using var client = new PveHttpClient(session);
|
||||
var response = client.PostAsync($"nodes/{node}/qemu/{vmid}/snapshot/{snapname}/rollback")
|
||||
var response = client.PostAsync($"nodes/{node}/qemu/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}/rollback")
|
||||
.GetAwaiter().GetResult();
|
||||
return ParseTask(response, node);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Backup;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Backup
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists Proxmox VE backup jobs.</para>
|
||||
/// <para type="description">
|
||||
/// Returns scheduled backup job configurations from the Proxmox VE cluster.
|
||||
/// Optionally filter by job ID.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveBackupJob")]
|
||||
[OutputType(typeof(PveBackupJob))]
|
||||
public sealed class GetPveBackupJobCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The backup job ID to retrieve. When omitted, all jobs are returned.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The backup job ID.")]
|
||||
public string? Id { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new BackupService();
|
||||
|
||||
if (!string.IsNullOrEmpty(Id))
|
||||
{
|
||||
WriteVerbose($"Getting backup job '{Id}'...");
|
||||
var job = service.GetBackupJob(session, Id!);
|
||||
WriteObject(job);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteVerbose("Getting all backup jobs...");
|
||||
var jobs = service.GetBackupJobs(session);
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
WriteObject(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Backup
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Creates an ad-hoc backup of a virtual machine via vzdump.</para>
|
||||
/// <para type="description">
|
||||
/// Triggers an immediate backup (vzdump) of the specified VM on the given node.
|
||||
/// Returns a PveTask representing the backup operation. Use -Wait to block until
|
||||
/// the backup completes.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "PveBackup", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(PveTask))]
|
||||
public sealed class NewPveBackupCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The node on which the VM resides.</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">The ID of the VM to back up.</para>
|
||||
/// </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 target storage for the backup.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The target storage for the backup.")]
|
||||
public string? Storage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The backup mode (snapshot, suspend, or stop).</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The backup mode.")]
|
||||
[ValidateSet("snapshot", "suspend", "stop")]
|
||||
public string? Mode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The compression algorithm to use.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The compression algorithm.")]
|
||||
[ValidateSet("zstd", "lzo", "gzip", "none")]
|
||||
public string? Compress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When specified, waits for the backup 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>
|
||||
/// <para type="description">Maximum seconds to wait when -Wait is specified. Default 3600.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Timeout in seconds for -Wait (default 3600).")]
|
||||
public int Timeout { get; set; } = 3600;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"VM {VmId} on node '{Node}'", "New-PveBackup"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new BackupService();
|
||||
|
||||
var formData = new Dictionary<string, string>
|
||||
{
|
||||
["vmid"] = VmId.ToString()
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(Storage)) formData["storage"] = Storage!;
|
||||
if (!string.IsNullOrEmpty(Mode)) formData["mode"] = Mode!;
|
||||
if (!string.IsNullOrEmpty(Compress)) formData["compress"] = Compress!;
|
||||
|
||||
WriteVerbose($"Creating backup of VM {VmId} on node '{Node}'...");
|
||||
var task = service.CreateBackup(session, Node, formData);
|
||||
|
||||
if (Wait.IsPresent)
|
||||
{
|
||||
var taskService = new TaskService();
|
||||
task = taskService.WaitForTask(session, task.Node ?? Node, task.Upid, TimeSpan.FromSeconds(Timeout));
|
||||
}
|
||||
|
||||
WriteObject(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Backup
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Creates a scheduled backup job on Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Creates a new scheduled backup job in the Proxmox VE cluster configuration.
|
||||
/// Specify a cron-style schedule and target storage. Use VmId for specific VMs
|
||||
/// or -All to back up all VMs.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "PveBackupJob", SupportsShouldProcess = true)]
|
||||
public sealed class NewPveBackupJobCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">Cron-style schedule expression (e.g., "0 2 * * *").</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "Cron-style schedule expression (e.g., '0 2 * * *').")]
|
||||
public string Schedule { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The target storage for backups.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The target storage for backups.")]
|
||||
public string Storage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The backup mode (snapshot, suspend, or stop).</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The backup mode.")]
|
||||
[ValidateSet("snapshot", "suspend", "stop")]
|
||||
public string? Mode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Comma-separated list of VM IDs to include in the backup job.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of VM IDs to back up.")]
|
||||
public string? VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When specified, backs up all VMs on the node.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Back up all VMs.")]
|
||||
public SwitchParameter All { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The compression algorithm to use.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The compression algorithm.")]
|
||||
[ValidateSet("zstd", "lzo", "gzip", "none")]
|
||||
public string? Compress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">The node to restrict the backup job to.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node to restrict the backup job to.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Email address(es) to send notifications to.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Email address(es) for notifications.")]
|
||||
public string? MailTo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When to send email notifications (always or failure).</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "When to send email notifications.")]
|
||||
[ValidateSet("always", "failure")]
|
||||
public string? MailNotification { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Whether the backup job is enabled. Default is true.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Whether the backup job is enabled.")]
|
||||
public SwitchParameter Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">A comment or description for the backup job.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "A comment or description for the backup job.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Prune-backups configuration string (e.g., "keep-daily=7,keep-weekly=4").</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Prune-backups configuration string.")]
|
||||
public string? PruneBackups { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess("cluster backup configuration", "New-PveBackupJob"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new BackupService();
|
||||
|
||||
var config = new Dictionary<string, string>
|
||||
{
|
||||
["schedule"] = Schedule,
|
||||
["storage"] = Storage,
|
||||
["enabled"] = Enabled.IsPresent ? "1" : "0"
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(Mode)) config["mode"] = Mode!;
|
||||
if (!string.IsNullOrEmpty(VmId)) config["vmid"] = VmId!;
|
||||
if (All.IsPresent) config["all"] = "1";
|
||||
if (!string.IsNullOrEmpty(Compress)) config["compress"] = Compress!;
|
||||
if (!string.IsNullOrEmpty(Node)) config["node"] = Node!;
|
||||
if (!string.IsNullOrEmpty(MailTo)) config["mailto"] = MailTo!;
|
||||
if (!string.IsNullOrEmpty(MailNotification)) config["mailnotification"] = MailNotification!;
|
||||
if (!string.IsNullOrEmpty(Comment)) config["comment"] = Comment!;
|
||||
if (!string.IsNullOrEmpty(PruneBackups)) config["prune-backups"] = PruneBackups!;
|
||||
|
||||
WriteVerbose("Creating backup job...");
|
||||
service.CreateBackupJob(session, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Backup
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Removes a scheduled backup job from Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Deletes a backup job from the Proxmox VE cluster configuration.
|
||||
/// This operation is destructive and requires confirmation unless -Force is specified.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "PveBackupJob",
|
||||
SupportsShouldProcess = true,
|
||||
ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemovePveBackupJobCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The backup job ID to remove.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The backup job ID.")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"backup job '{Id}'", "Remove-PveBackupJob"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new BackupService();
|
||||
|
||||
WriteVerbose($"Removing backup job '{Id}'...");
|
||||
service.RemoveBackupJob(session, Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Backup
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Updates a scheduled backup job on Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Modifies properties of an existing backup job. Only specified parameters are
|
||||
/// updated; omitted parameters retain their current values.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "PveBackupJob", SupportsShouldProcess = true)]
|
||||
public sealed class SetPveBackupJobCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="description">The backup job ID to update.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The backup job ID.")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Updated cron-style schedule expression.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Cron-style schedule expression (e.g., '0 2 * * *').")]
|
||||
public string? Schedule { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Updated target storage for backups.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The target storage for backups.")]
|
||||
public string? Storage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Updated backup mode (snapshot, suspend, or stop).</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The backup mode.")]
|
||||
[ValidateSet("snapshot", "suspend", "stop")]
|
||||
public string? Mode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Updated comma-separated list of VM IDs.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of VM IDs to back up.")]
|
||||
public string? VmId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">When specified, backs up all VMs.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Back up all VMs.")]
|
||||
public SwitchParameter All { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Updated compression algorithm.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The compression algorithm.")]
|
||||
[ValidateSet("zstd", "lzo", "gzip", "none")]
|
||||
public string? Compress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Updated node restriction.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node to restrict the backup job to.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Updated email address(es) for notifications.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Email address(es) for notifications.")]
|
||||
public string? MailTo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Updated notification policy.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "When to send email notifications.")]
|
||||
[ValidateSet("always", "failure")]
|
||||
public string? MailNotification { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Enable or disable the backup job.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Enable or disable the backup job.")]
|
||||
public SwitchParameter Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Updated comment or description.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "A comment or description for the backup job.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Updated prune-backups configuration string.</para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Prune-backups configuration string.")]
|
||||
public string? PruneBackups { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"backup job '{Id}'", "Set-PveBackupJob"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new BackupService();
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Schedule)) config["schedule"] = Schedule!;
|
||||
if (!string.IsNullOrEmpty(Storage)) config["storage"] = Storage!;
|
||||
if (!string.IsNullOrEmpty(Mode)) config["mode"] = Mode!;
|
||||
if (!string.IsNullOrEmpty(VmId)) config["vmid"] = VmId!;
|
||||
if (All.IsPresent) config["all"] = "1";
|
||||
if (!string.IsNullOrEmpty(Compress)) config["compress"] = Compress!;
|
||||
if (!string.IsNullOrEmpty(Node)) config["node"] = Node!;
|
||||
if (!string.IsNullOrEmpty(MailTo)) config["mailto"] = MailTo!;
|
||||
if (!string.IsNullOrEmpty(MailNotification)) config["mailnotification"] = MailNotification!;
|
||||
if (MyInvocation.BoundParameters.ContainsKey("Enabled")) config["enabled"] = Enabled.IsPresent ? "1" : "0";
|
||||
if (!string.IsNullOrEmpty(Comment)) config["comment"] = Comment!;
|
||||
if (!string.IsNullOrEmpty(PruneBackups)) config["prune-backups"] = PruneBackups!;
|
||||
|
||||
WriteVerbose($"Updating backup job '{Id}'...");
|
||||
service.UpdateBackupJob(session, Id, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "PveFirewallAlias")]
|
||||
[OutputType(typeof(PveFirewallAlias))]
|
||||
public class GetPveFirewallAliasCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Optional alias name to filter by.")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Getting firewall aliases at level '{level}'...");
|
||||
var aliases = service.GetAliases(session, level, Node, vmid);
|
||||
|
||||
if (!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
var filtered = aliases.Where(a => string.Equals(a.Name, Name, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
WriteObject(filtered, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteObject(aliases, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "PveFirewallGroup")]
|
||||
[OutputType(typeof(PveFirewallGroup), typeof(PveFirewallRule))]
|
||||
public class GetPveFirewallGroupCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = false, Position = 0, HelpMessage = "The security group name. If specified, returns rules within the group.")]
|
||||
public string? Group { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
|
||||
if (!string.IsNullOrEmpty(Group))
|
||||
{
|
||||
WriteVerbose($"Getting firewall rules for security group '{Group}'...");
|
||||
var rules = service.GetGroupRules(session, Group!);
|
||||
WriteObject(rules, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteVerbose("Getting firewall security groups...");
|
||||
var groups = service.GetGroups(session);
|
||||
WriteObject(groups, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "PveFirewallIpSet")]
|
||||
[OutputType(typeof(PveFirewallIpSet))]
|
||||
public class GetPveFirewallIpSetCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Optional IP set name to filter by.")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Getting firewall IP sets at level '{level}'...");
|
||||
var ipSets = service.GetIpSets(session, level, Node, vmid);
|
||||
|
||||
if (!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
var filtered = ipSets.Where(s => string.Equals(s.Name, Name, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
WriteObject(filtered, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteObject(ipSets, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "PveFirewallIpSetEntry")]
|
||||
[OutputType(typeof(PveFirewallIpSetEntry))]
|
||||
public class GetPveFirewallIpSetEntryCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The IP set name.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Getting entries for IP set '{Name}' at level '{level}'...");
|
||||
var entries = service.GetIpSetEntries(session, level, Name, Node, vmid);
|
||||
WriteObject(entries, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "PveFirewallOptions")]
|
||||
[OutputType(typeof(PveFirewallOptions))]
|
||||
public class GetPveFirewallOptionsCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Getting firewall options at level '{level}'...");
|
||||
var options = service.GetOptions(session, level, Node, vmid);
|
||||
WriteObject(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "PveFirewallRef")]
|
||||
[OutputType(typeof(PveFirewallRef))]
|
||||
public class GetPveFirewallRefCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Optional type filter for references.")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Getting firewall references at level '{level}'...");
|
||||
var refs = service.GetRefs(session, level, Type, Node, vmid);
|
||||
WriteObject(refs, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "PveFirewallRule")]
|
||||
[OutputType(typeof(PveFirewallRule))]
|
||||
public class GetPveFirewallRuleCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Optional rule position to filter by.")]
|
||||
public int? Position { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Getting firewall rules at level '{level}'...");
|
||||
var rules = service.GetRules(session, level, Node, vmid);
|
||||
|
||||
if (Position.HasValue)
|
||||
{
|
||||
var filtered = rules.Where(r => r.Pos == Position.Value).ToArray();
|
||||
WriteObject(filtered, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteObject(rules, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.New, "PveFirewallAlias", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(void))]
|
||||
public class NewPveFirewallAliasCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The alias name.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The CIDR network address.")]
|
||||
public string Cidr { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Optional comment for the alias.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"firewall alias '{Name}' ({Level})", "Create"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Creating firewall alias '{Name}' at level '{level}'...");
|
||||
service.CreateAlias(session, level, Name, Cidr, Comment, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.New, "PveFirewallGroup", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(void))]
|
||||
public class NewPveFirewallGroupCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The security group name.")]
|
||||
public string Group { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Optional comment for the security group.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"firewall security group '{Group}'", "Create"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
|
||||
WriteVerbose($"Creating firewall security group '{Group}'...");
|
||||
service.CreateGroup(session, Group, Comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.New, "PveFirewallIpSet", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(void))]
|
||||
public class NewPveFirewallIpSetCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The IP set name.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Optional comment for the IP set.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"firewall IP set '{Name}' ({Level})", "Create"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Creating firewall IP set '{Name}' at level '{level}'...");
|
||||
service.CreateIpSet(session, level, Name, Comment, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.New, "PveFirewallIpSetEntry", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(void))]
|
||||
public class NewPveFirewallIpSetEntryCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The IP set name.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The CIDR network address to add.")]
|
||||
public string Cidr { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Exclude this entry from the IP set match.")]
|
||||
public SwitchParameter NoMatch { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Optional comment for the entry.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"IP set entry '{Cidr}' in '{Name}' ({Level})", "Create"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Adding entry '{Cidr}' to IP set '{Name}' at level '{level}'...");
|
||||
service.AddIpSetEntry(session, level, Name, Cidr, NoMatch.IsPresent, Comment, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.New, "PveFirewallRule", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(PveFirewallRule))]
|
||||
public class NewPveFirewallRuleCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The rule type: in, out, or group.")]
|
||||
[ValidateSet("in", "out", "group")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The rule action: ACCEPT, DROP, or REJECT.")]
|
||||
[ValidateSet("ACCEPT", "DROP", "REJECT")]
|
||||
public string Action { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Enable the rule.")]
|
||||
public SwitchParameter Enable { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Source address or alias.")]
|
||||
public string? Source { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Destination address or alias.")]
|
||||
public string? Dest { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Protocol (e.g., tcp, udp, icmp).")]
|
||||
public string? Proto { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Destination port.")]
|
||||
public string? Dport { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Source port.")]
|
||||
public string? Sport { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Rule comment.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Macro name.")]
|
||||
public string? Macro { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Log level.")]
|
||||
public string? Log { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Network interface.")]
|
||||
public string? Iface { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"firewall rule ({Level})", "Create"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
var config = new Dictionary<string, string>
|
||||
{
|
||||
["type"] = Type,
|
||||
["action"] = Action
|
||||
};
|
||||
|
||||
if (Enable.IsPresent)
|
||||
config["enable"] = "1";
|
||||
if (!string.IsNullOrEmpty(Source))
|
||||
config["source"] = Source!;
|
||||
if (!string.IsNullOrEmpty(Dest))
|
||||
config["dest"] = Dest!;
|
||||
if (!string.IsNullOrEmpty(Proto))
|
||||
config["proto"] = Proto!;
|
||||
if (!string.IsNullOrEmpty(Dport))
|
||||
config["dport"] = Dport!;
|
||||
if (!string.IsNullOrEmpty(Sport))
|
||||
config["sport"] = Sport!;
|
||||
if (!string.IsNullOrEmpty(Comment))
|
||||
config["comment"] = Comment!;
|
||||
if (!string.IsNullOrEmpty(Macro))
|
||||
config["macro"] = Macro!;
|
||||
if (!string.IsNullOrEmpty(Log))
|
||||
config["log"] = Log!;
|
||||
if (!string.IsNullOrEmpty(Iface))
|
||||
config["iface"] = Iface!;
|
||||
|
||||
WriteVerbose($"Creating firewall rule at level '{level}'...");
|
||||
service.CreateRule(session, level, config, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Remove, "PveFirewallAlias", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
[OutputType(typeof(void))]
|
||||
public class RemovePveFirewallAliasCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The alias name to remove.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"firewall alias '{Name}' ({Level})", "Remove"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Removing firewall alias '{Name}' at level '{level}'...");
|
||||
service.RemoveAlias(session, level, Name, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Remove, "PveFirewallGroup", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
[OutputType(typeof(void))]
|
||||
public class RemovePveFirewallGroupCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The security group name to remove.")]
|
||||
public string Group { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"firewall security group '{Group}'", "Remove"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
|
||||
WriteVerbose($"Removing firewall security group '{Group}'...");
|
||||
service.RemoveGroup(session, Group);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Remove, "PveFirewallIpSet", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
[OutputType(typeof(void))]
|
||||
public class RemovePveFirewallIpSetCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The IP set name to remove.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"firewall IP set '{Name}' ({Level})", "Remove"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Removing firewall IP set '{Name}' at level '{level}'...");
|
||||
service.RemoveIpSet(session, level, Name, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Remove, "PveFirewallIpSetEntry", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
[OutputType(typeof(void))]
|
||||
public class RemovePveFirewallIpSetEntryCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The IP set name.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The CIDR network address to remove.")]
|
||||
public string Cidr { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"IP set entry '{Cidr}' from '{Name}' ({Level})", "Remove"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Removing entry '{Cidr}' from IP set '{Name}' at level '{level}'...");
|
||||
service.RemoveIpSetEntry(session, level, Name, Cidr, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Remove, "PveFirewallRule", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
[OutputType(typeof(void))]
|
||||
public class RemovePveFirewallRuleCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The rule position to remove.")]
|
||||
public int Position { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"firewall rule at position {Position} ({Level})", "Remove"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Removing firewall rule at position {Position} ({level})...");
|
||||
service.RemoveRule(session, level, Position, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Set, "PveFirewallAlias", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(void))]
|
||||
public class SetPveFirewallAliasCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The alias name to update.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The updated CIDR network address.")]
|
||||
public string? Cidr { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Updated comment for the alias.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"firewall alias '{Name}' ({Level})", "Update"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
WriteVerbose($"Updating firewall alias '{Name}' at level '{level}'...");
|
||||
service.UpdateAlias(session, level, Name, Cidr, Comment, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Set, "PveFirewallIpSetEntry", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(void))]
|
||||
public class SetPveFirewallIpSetEntryCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The IP set name.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The CIDR network address to update.")]
|
||||
public string Cidr { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Exclude this entry from the IP set match.")]
|
||||
public SwitchParameter NoMatch { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Updated comment for the entry.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"IP set entry '{Cidr}' in '{Name}' ({Level})", "Update"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
bool? nomatch = NoMatch.IsPresent ? true : (bool?)null;
|
||||
|
||||
WriteVerbose($"Updating entry '{Cidr}' in IP set '{Name}' at level '{level}'...");
|
||||
service.UpdateIpSetEntry(session, level, Name, Cidr, nomatch, Comment, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Set, "PveFirewallOptions", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(void))]
|
||||
public class SetPveFirewallOptionsCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Enable or disable the firewall.")]
|
||||
public SwitchParameter Enable { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Default policy for incoming traffic: ACCEPT, DROP, or REJECT.")]
|
||||
[ValidateSet("ACCEPT", "DROP", "REJECT")]
|
||||
public string? PolicyIn { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Default policy for outgoing traffic: ACCEPT, DROP, or REJECT.")]
|
||||
[ValidateSet("ACCEPT", "DROP", "REJECT")]
|
||||
public string? PolicyOut { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Log level for incoming traffic.")]
|
||||
public string? LogLevelIn { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Log level for outgoing traffic.")]
|
||||
public string? LogLevelOut { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Enable DHCP.")]
|
||||
public SwitchParameter Dhcp { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Enable NDP.")]
|
||||
public SwitchParameter Ndp { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Enable MAC address filter.")]
|
||||
public SwitchParameter MacFilter { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Enable IP filter.")]
|
||||
public SwitchParameter IpFilter { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"firewall options ({Level})", "Update"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (Enable.IsPresent)
|
||||
config["enable"] = "1";
|
||||
if (!string.IsNullOrEmpty(PolicyIn))
|
||||
config["policy_in"] = PolicyIn!;
|
||||
if (!string.IsNullOrEmpty(PolicyOut))
|
||||
config["policy_out"] = PolicyOut!;
|
||||
if (!string.IsNullOrEmpty(LogLevelIn))
|
||||
config["log_level_in"] = LogLevelIn!;
|
||||
if (!string.IsNullOrEmpty(LogLevelOut))
|
||||
config["log_level_out"] = LogLevelOut!;
|
||||
if (Dhcp.IsPresent)
|
||||
config["dhcp"] = "1";
|
||||
if (Ndp.IsPresent)
|
||||
config["ndp"] = "1";
|
||||
if (MacFilter.IsPresent)
|
||||
config["macfilter"] = "1";
|
||||
if (IpFilter.IsPresent)
|
||||
config["ipfilter"] = "1";
|
||||
|
||||
WriteVerbose($"Setting firewall options at level '{level}'...");
|
||||
service.SetOptions(session, level, config, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Firewall
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Set, "PveFirewallRule", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(void))]
|
||||
public class SetPveFirewallRuleCmdlet : PveCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
|
||||
[ValidateSet("Cluster", "Node", "Vm", "Container")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
|
||||
public string? Node { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
|
||||
[ValidateRange(100, 999999999)]
|
||||
public int VmId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, HelpMessage = "The rule position to update.")]
|
||||
public int Position { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The rule type: in, out, or group.")]
|
||||
[ValidateSet("in", "out", "group")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "The rule action: ACCEPT, DROP, or REJECT.")]
|
||||
[ValidateSet("ACCEPT", "DROP", "REJECT")]
|
||||
public string? Action { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Enable the rule.")]
|
||||
public SwitchParameter Enable { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Source address or alias.")]
|
||||
public string? Source { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Destination address or alias.")]
|
||||
public string? Dest { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Protocol (e.g., tcp, udp, icmp).")]
|
||||
public string? Proto { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Destination port.")]
|
||||
public string? Dport { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Source port.")]
|
||||
public string? Sport { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Rule comment.")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Macro name.")]
|
||||
public string? Macro { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Log level.")]
|
||||
public string? Log { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false, HelpMessage = "Network interface.")]
|
||||
public string? Iface { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var level = Level;
|
||||
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Node))
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("Node is required when Level is not Cluster."),
|
||||
"NodeRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (VmId == 0)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException("VmId is required when Level is Vm or Container."),
|
||||
"VmIdRequired", ErrorCategory.InvalidArgument, null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShouldProcess($"firewall rule at position {Position} ({Level})", "Update"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new FirewallService();
|
||||
int? vmid = VmId > 0 ? VmId : (int?)null;
|
||||
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Type))
|
||||
config["type"] = Type!;
|
||||
if (!string.IsNullOrEmpty(Action))
|
||||
config["action"] = Action!;
|
||||
if (Enable.IsPresent)
|
||||
config["enable"] = "1";
|
||||
if (!string.IsNullOrEmpty(Source))
|
||||
config["source"] = Source!;
|
||||
if (!string.IsNullOrEmpty(Dest))
|
||||
config["dest"] = Dest!;
|
||||
if (!string.IsNullOrEmpty(Proto))
|
||||
config["proto"] = Proto!;
|
||||
if (!string.IsNullOrEmpty(Dport))
|
||||
config["dport"] = Dport!;
|
||||
if (!string.IsNullOrEmpty(Sport))
|
||||
config["sport"] = Sport!;
|
||||
if (!string.IsNullOrEmpty(Comment))
|
||||
config["comment"] = Comment!;
|
||||
if (!string.IsNullOrEmpty(Macro))
|
||||
config["macro"] = Macro!;
|
||||
if (!string.IsNullOrEmpty(Log))
|
||||
config["log"] = Log!;
|
||||
if (!string.IsNullOrEmpty(Iface))
|
||||
config["iface"] = Iface!;
|
||||
|
||||
WriteVerbose($"Updating firewall rule at position {Position} ({level})...");
|
||||
service.UpdateRule(session, level, Position, config, Node, vmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Network;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists SDN controllers in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Returns Software-Defined Networking controller definitions.
|
||||
/// Optionally filters by controller identifier.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveSdnController")]
|
||||
[OutputType(typeof(PveSdnController))]
|
||||
public class GetPveSdnControllerCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>Optional controller identifier filter.</summary>
|
||||
[Parameter(Mandatory = false, Position = 0, HelpMessage = "Filter by controller identifier.")]
|
||||
public string? Controller { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose("Getting SDN controllers...");
|
||||
var controllers = service.GetSdnControllers(session);
|
||||
|
||||
foreach (var item in controllers)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Controller) &&
|
||||
!string.Equals(item.Controller, Controller, System.StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
WriteObject(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Network;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists SDN DNS plugins in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Returns Software-Defined Networking DNS plugin definitions.
|
||||
/// Optionally filters by DNS identifier.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveSdnDns")]
|
||||
[OutputType(typeof(PveSdnDns))]
|
||||
public class GetPveSdnDnsCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>Optional DNS plugin identifier filter.</summary>
|
||||
[Parameter(Mandatory = false, Position = 0, HelpMessage = "Filter by DNS plugin identifier.")]
|
||||
public string? Dns { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose("Getting SDN DNS plugins...");
|
||||
var plugins = service.GetSdnDnsPlugins(session);
|
||||
|
||||
foreach (var item in plugins)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Dns) &&
|
||||
!string.Equals(item.Dns, Dns, System.StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
WriteObject(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Models.Network;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists SDN IPAM plugins in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Returns Software-Defined Networking IPAM plugin definitions.
|
||||
/// Optionally filters by IPAM identifier.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "PveSdnIpam")]
|
||||
[OutputType(typeof(PveSdnIpam))]
|
||||
public class GetPveSdnIpamCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>Optional IPAM identifier filter.</summary>
|
||||
[Parameter(Mandatory = false, Position = 0, HelpMessage = "Filter by IPAM plugin identifier.")]
|
||||
public string? Ipam { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
var session = GetSession();
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose("Getting SDN IPAM plugins...");
|
||||
var ipams = service.GetSdnIpams(session);
|
||||
|
||||
foreach (var item in ipams)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Ipam) &&
|
||||
!string.Equals(item.Ipam, Ipam, System.StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
WriteObject(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Creates a new SDN controller in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Adds a new Software-Defined Networking controller.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "PveSdnController", SupportsShouldProcess = true)]
|
||||
public class NewPveSdnControllerCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The controller identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The controller identifier.")]
|
||||
public string Controller { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The controller type.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The controller type.")]
|
||||
[ValidateSet("evpn", "bgp")]
|
||||
public string Type { 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($"SDN Controller '{Controller}'", "Create PVE SDN Controller"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Creating SDN controller '{Controller}' of type '{Type}'...");
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
["controller"] = Controller,
|
||||
["type"] = Type
|
||||
};
|
||||
|
||||
if (Asn.HasValue) data["asn"] = Asn.Value.ToString();
|
||||
if (!string.IsNullOrEmpty(Peers)) data["peers"] = Peers!;
|
||||
if (!string.IsNullOrEmpty(Node)) data["node"] = Node!;
|
||||
|
||||
service.CreateSdnController(session, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Creates a new SDN DNS plugin in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Adds a new Software-Defined Networking DNS plugin.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "PveSdnDns", SupportsShouldProcess = true)]
|
||||
public class NewPveSdnDnsCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The DNS plugin identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The DNS plugin identifier.")]
|
||||
public string Dns { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The DNS plugin type.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The DNS plugin type.")]
|
||||
[ValidateSet("powerdns")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The URL of the DNS service API.</summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The URL of the DNS service API.")]
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The API key for the DNS service.</summary>
|
||||
[Parameter(Mandatory = true, HelpMessage = "The API key for the DNS service.")]
|
||||
public string Key { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The IPv6 reverse zone mask length.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The IPv6 reverse zone mask length.")]
|
||||
public int? ReverseMaskV6 { get; set; }
|
||||
|
||||
/// <summary>The TTL (time-to-live) for DNS records.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The TTL (time-to-live) for DNS records.")]
|
||||
public int? Ttl { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"SDN DNS '{Dns}'", "Create PVE SDN DNS"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Creating SDN DNS plugin '{Dns}' of type '{Type}'...");
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
["dns"] = Dns,
|
||||
["type"] = Type,
|
||||
["url"] = Url,
|
||||
["key"] = Key
|
||||
};
|
||||
|
||||
if (ReverseMaskV6.HasValue) data["reversemaskv6"] = ReverseMaskV6.Value.ToString();
|
||||
if (Ttl.HasValue) data["ttl"] = Ttl.Value.ToString();
|
||||
|
||||
service.CreateSdnDnsPlugin(session, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Creates a new SDN IPAM plugin in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Adds a new Software-Defined Networking IPAM plugin.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "PveSdnIpam", SupportsShouldProcess = true)]
|
||||
public class NewPveSdnIpamCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The IPAM plugin identifier.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The IPAM plugin identifier.")]
|
||||
public string Ipam { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The IPAM plugin type.</summary>
|
||||
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The IPAM plugin type.")]
|
||||
[ValidateSet("pve", "netbox", "phpipam")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The URL of the external IPAM service.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The URL of the external IPAM service.")]
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>The API token for the external IPAM service.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The API token for the external IPAM service.")]
|
||||
public string? Token { get; set; }
|
||||
|
||||
/// <summary>The configuration section identifier.</summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "The configuration section identifier.")]
|
||||
public int? Section { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"SDN IPAM '{Ipam}'", "Create PVE SDN IPAM"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Creating SDN IPAM plugin '{Ipam}' of type '{Type}'...");
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
["ipam"] = Ipam,
|
||||
["type"] = Type
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(Url)) data["url"] = Url!;
|
||||
if (!string.IsNullOrEmpty(Token)) data["token"] = Token!;
|
||||
if (Section.HasValue) data["section"] = Section.Value.ToString();
|
||||
|
||||
service.CreateSdnIpam(session, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Removes an SDN controller from Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Deletes the specified Software-Defined Networking controller.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "PveSdnController", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public class RemovePveSdnControllerCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The controller identifier to remove.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The controller identifier to remove.")]
|
||||
public string Controller { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"SDN Controller '{Controller}'", "Remove PVE SDN Controller"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Removing SDN controller '{Controller}'...");
|
||||
service.RemoveSdnController(session, Controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Removes an SDN DNS plugin from Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Deletes the specified Software-Defined Networking DNS plugin.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "PveSdnDns", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public class RemovePveSdnDnsCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The DNS plugin identifier to remove.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The DNS plugin identifier to remove.")]
|
||||
public string Dns { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"SDN DNS '{Dns}'", "Remove PVE SDN DNS"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Removing SDN DNS plugin '{Dns}'...");
|
||||
service.RemoveSdnDnsPlugin(session, Dns);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Management.Automation;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Removes an SDN IPAM plugin from Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Deletes the specified Software-Defined Networking IPAM plugin.
|
||||
/// Requires Proxmox VE 8.0 or later.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "PveSdnIpam", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public class RemovePveSdnIpamCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The IPAM plugin identifier to remove.</summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The IPAM plugin identifier to remove.")]
|
||||
public string Ipam { get; set; } = string.Empty;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (!ShouldProcess($"SDN IPAM '{Ipam}'", "Remove PVE SDN IPAM"))
|
||||
return;
|
||||
|
||||
var session = GetSession();
|
||||
var service = new NetworkService();
|
||||
|
||||
WriteVerbose($"Removing SDN IPAM plugin '{Ipam}'...");
|
||||
service.RemoveSdnIpam(session, Ipam);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace PSProxmoxVE.Cmdlets.Users
|
||||
/// Built-in roles cannot be removed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Remove, "PveRole", SupportsShouldProcess = true)]
|
||||
[Cmdlet(VerbsCommon.Remove, "PveRole", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public class RemovePveRoleCmdlet : PveCmdletBase
|
||||
{
|
||||
/// <summary>The role identifier to remove.</summary>
|
||||
|
||||
@@ -442,5 +442,507 @@
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallRule -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallRule</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallRule</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Pos</Label>
|
||||
<Width>5</Width>
|
||||
<Alignment>Right</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Type</Label>
|
||||
<Width>6</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Action</Label>
|
||||
<Width>8</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Proto</Label>
|
||||
<Width>6</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Dport</Label>
|
||||
<Width>12</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Source</Label>
|
||||
<Width>20</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Dest</Label>
|
||||
<Width>20</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Enable</Label>
|
||||
<Width>7</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Comment</Label>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Pos</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Type</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Action</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Proto</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Dport</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Source</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Dest</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.Enable -eq 1) { 'Yes' } else { 'No' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Comment</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallGroup -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallGroup</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallGroup</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Group</Label>
|
||||
<Width>25</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Comment</Label>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Group</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Comment</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallAlias -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallAlias</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallAlias</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Name</Label>
|
||||
<Width>25</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Cidr</Label>
|
||||
<Width>25</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Comment</Label>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Name</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Cidr</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Comment</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSet -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSet</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSet</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Name</Label>
|
||||
<Width>25</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Comment</Label>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Name</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Comment</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSetEntry -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSetEntry</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSetEntry</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Cidr</Label>
|
||||
<Width>25</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>NoMatch</Label>
|
||||
<Width>8</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Comment</Label>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Cidr</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.NoMatch -eq 1) { 'Yes' } else { 'No' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Comment</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallOptions -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallOptions</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallOptions</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Enable</Label>
|
||||
<Width>8</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>PolicyIn</Label>
|
||||
<Width>10</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>PolicyOut</Label>
|
||||
<Width>10</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>LogLevelIn</Label>
|
||||
<Width>12</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>LogLevelOut</Label>
|
||||
<Width>12</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.Enable -eq 1) { 'Yes' } else { 'No' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>PolicyIn</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>PolicyOut</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>LogLevelIn</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>LogLevelOut</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Backup.PveBackupJob -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Backup.PveBackupJob</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Backup.PveBackupJob</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Id</Label>
|
||||
<Width>20</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Enabled</Label>
|
||||
<Width>8</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Schedule</Label>
|
||||
<Width>16</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Storage</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Mode</Label>
|
||||
<Width>10</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>VmId</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Comment</Label>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Id</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>if ($_.Enabled -eq 1) { 'Yes' } else { 'No' }</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Schedule</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Storage</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Mode</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>VmId</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Comment</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Network.PveSdnIpam -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Network.PveSdnIpam</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Network.PveSdnIpam</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Ipam</Label>
|
||||
<Width>20</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Type</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Ipam</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Type</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Network.PveSdnDns -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Network.PveSdnDns</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Network.PveSdnDns</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Dns</Label>
|
||||
<Width>20</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Type</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Url</Label>
|
||||
<Width>40</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Dns</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Type</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Url</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
<!-- PSProxmoxVE.Core.Models.Network.PveSdnController -->
|
||||
<View>
|
||||
<Name>PSProxmoxVE.Core.Models.Network.PveSdnController</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSProxmoxVE.Core.Models.Network.PveSdnController</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Label>Controller</Label>
|
||||
<Width>20</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Type</Label>
|
||||
<Width>10</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Asn</Label>
|
||||
<Width>10</Width>
|
||||
<Alignment>Right</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Peers</Label>
|
||||
<Width>30</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Node</Label>
|
||||
<Width>15</Width>
|
||||
<Alignment>Left</Alignment>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Controller</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Type</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Asn</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Peers</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Node</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
|
||||
</ViewDefinitions>
|
||||
</Configuration>
|
||||
|
||||
@@ -170,7 +170,52 @@
|
||||
|
||||
# Tasks
|
||||
'Get-PveTask',
|
||||
'Wait-PveTask'
|
||||
'Wait-PveTask',
|
||||
|
||||
# Firewall
|
||||
'Get-PveFirewallRule',
|
||||
'New-PveFirewallRule',
|
||||
'Set-PveFirewallRule',
|
||||
'Remove-PveFirewallRule',
|
||||
'Get-PveFirewallGroup',
|
||||
'New-PveFirewallGroup',
|
||||
'Remove-PveFirewallGroup',
|
||||
'Get-PveFirewallAlias',
|
||||
'New-PveFirewallAlias',
|
||||
'Set-PveFirewallAlias',
|
||||
'Remove-PveFirewallAlias',
|
||||
'Get-PveFirewallIpSet',
|
||||
'New-PveFirewallIpSet',
|
||||
'Remove-PveFirewallIpSet',
|
||||
'Get-PveFirewallIpSetEntry',
|
||||
'New-PveFirewallIpSetEntry',
|
||||
'Set-PveFirewallIpSetEntry',
|
||||
'Remove-PveFirewallIpSetEntry',
|
||||
'Get-PveFirewallOptions',
|
||||
'Set-PveFirewallOptions',
|
||||
'Get-PveFirewallRef',
|
||||
|
||||
# Backup
|
||||
'New-PveBackup',
|
||||
'Get-PveBackupJob',
|
||||
'New-PveBackupJob',
|
||||
'Set-PveBackupJob',
|
||||
'Remove-PveBackupJob',
|
||||
|
||||
# SDN — IPAM
|
||||
'Get-PveSdnIpam',
|
||||
'New-PveSdnIpam',
|
||||
'Remove-PveSdnIpam',
|
||||
|
||||
# SDN — DNS
|
||||
'Get-PveSdnDns',
|
||||
'New-PveSdnDns',
|
||||
'Remove-PveSdnDns',
|
||||
|
||||
# SDN — Controller
|
||||
'Get-PveSdnController',
|
||||
'New-PveSdnController',
|
||||
'Remove-PveSdnController'
|
||||
)
|
||||
|
||||
# Variables to export from this module
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"data":[{"id":"backup-1a2b3c","type":"vzdump","enabled":1,"schedule":"0 2 * * *","storage":"local","mode":"snapshot","compress":"zstd","vmid":"100,101","comment":"Nightly backup"},{"id":"backup-4d5e6f","type":"vzdump","enabled":0,"schedule":"0 4 * * 0","storage":"nfs-backup","mode":"stop","all":1,"comment":"Weekly full"}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"data":[{"name":"local_network","cidr":"10.0.0.0/8","comment":"RFC1918 private"},{"name":"dns_server","cidr":"8.8.8.8","comment":"Google DNS"}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"data":[{"group":"webservers","comment":"HTTP/HTTPS servers","digest":"abc123"},{"group":"dbservers","comment":"Database servers","digest":"def456"}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"data":[{"cidr":"192.168.1.0/24","nomatch":0,"comment":"Local subnet"},{"cidr":"10.0.0.0/8","nomatch":1,"comment":"Exclude private"}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"data":[{"name":"blocklist","comment":"Blocked IPs","digest":"aaa111"},{"name":"allowlist","comment":"Allowed IPs","digest":"bbb222"}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"data":{"enable":1,"policy_in":"DROP","policy_out":"ACCEPT","log_level_in":"nolog","log_level_out":"nolog"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"data":[{"type":"alias","name":"local_network","comment":"RFC1918 private"},{"type":"ipset","name":"blocklist","comment":"Blocked IPs"}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"data":[{"pos":0,"type":"in","action":"ACCEPT","proto":"tcp","dport":"80","enable":1,"comment":"Allow HTTP","log":"nolog"},{"pos":1,"type":"in","action":"ACCEPT","proto":"tcp","dport":"443","enable":1,"comment":"Allow HTTPS"},{"pos":2,"type":"out","action":"DROP","enable":0,"comment":"Block outbound (disabled)"}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"data":[{"controller":"evpn1","type":"evpn","asn":65000,"peers":"10.0.0.1,10.0.0.2","node":"pve1"}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"data":[{"dns":"powerdns1","type":"powerdns","url":"http://dns.example.com:8081","key":"secret123","ttl":3600}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"data":[{"ipam":"pve","type":"pve"},{"ipam":"netbox1","type":"netbox","url":"https://netbox.example.com","token":"abc123","section":1}]}
|
||||
@@ -0,0 +1,126 @@
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PSProxmoxVE.Core.Models.Backup;
|
||||
|
||||
namespace PSProxmoxVE.Core.Tests.Models
|
||||
{
|
||||
public class BackupModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void PveBackupJob_Deserialize_Pve9_ReturnsCorrectCount()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_backup_jobs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var jobs = data.ToObject<PveBackupJob[]>();
|
||||
Assert.NotNull(jobs);
|
||||
Assert.Equal(2, jobs.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveBackupJob_Deserialize_Pve9_FirstJob_HasCorrectId()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_backup_jobs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var jobs = data.ToObject<PveBackupJob[]>();
|
||||
Assert.NotNull(jobs);
|
||||
Assert.Equal("backup-1a2b3c", jobs[0].Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveBackupJob_Deserialize_Pve9_FirstJob_IsEnabled()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_backup_jobs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var jobs = data.ToObject<PveBackupJob[]>();
|
||||
Assert.NotNull(jobs);
|
||||
Assert.Equal(1, jobs[0].Enabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveBackupJob_Deserialize_Pve9_FirstJob_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_backup_jobs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var jobs = data.ToObject<PveBackupJob[]>();
|
||||
Assert.NotNull(jobs);
|
||||
Assert.Equal("vzdump", jobs[0].Type);
|
||||
Assert.Equal("0 2 * * *", jobs[0].Schedule);
|
||||
Assert.Equal("local", jobs[0].Storage);
|
||||
Assert.Equal("snapshot", jobs[0].Mode);
|
||||
Assert.Equal("zstd", jobs[0].Compress);
|
||||
Assert.Equal("100,101", jobs[0].VmId);
|
||||
Assert.Equal("Nightly backup", jobs[0].Comment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveBackupJob_Deserialize_Pve9_FirstJob_AllIsNull()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_backup_jobs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var jobs = data.ToObject<PveBackupJob[]>();
|
||||
Assert.NotNull(jobs);
|
||||
Assert.Null(jobs[0].All);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveBackupJob_Deserialize_Pve9_SecondJob_IsDisabled()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_backup_jobs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var jobs = data.ToObject<PveBackupJob[]>();
|
||||
Assert.NotNull(jobs);
|
||||
Assert.Equal(0, jobs[1].Enabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveBackupJob_Deserialize_Pve9_SecondJob_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_backup_jobs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var jobs = data.ToObject<PveBackupJob[]>();
|
||||
Assert.NotNull(jobs);
|
||||
Assert.Equal("backup-4d5e6f", jobs[1].Id);
|
||||
Assert.Equal("vzdump", jobs[1].Type);
|
||||
Assert.Equal("0 4 * * 0", jobs[1].Schedule);
|
||||
Assert.Equal("nfs-backup", jobs[1].Storage);
|
||||
Assert.Equal("stop", jobs[1].Mode);
|
||||
Assert.Equal("Weekly full", jobs[1].Comment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveBackupJob_Deserialize_Pve9_SecondJob_BacksUpAll()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_backup_jobs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var jobs = data.ToObject<PveBackupJob[]>();
|
||||
Assert.NotNull(jobs);
|
||||
Assert.Equal(1, jobs[1].All);
|
||||
Assert.Null(jobs[1].VmId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveBackupJob_Deserialize_Pve9_SecondJob_OptionalFieldsAreNull()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_backup_jobs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var jobs = data.ToObject<PveBackupJob[]>();
|
||||
Assert.NotNull(jobs);
|
||||
Assert.Null(jobs[1].Compress);
|
||||
Assert.Null(jobs[1].MaxFiles);
|
||||
Assert.Null(jobs[1].PruneBackups);
|
||||
Assert.Null(jobs[1].MailNotification);
|
||||
Assert.Null(jobs[1].MailTo);
|
||||
Assert.Null(jobs[1].Node);
|
||||
Assert.Null(jobs[1].Exclude);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PSProxmoxVE.Core.Models.Firewall;
|
||||
|
||||
namespace PSProxmoxVE.Core.Tests.Models
|
||||
{
|
||||
public class FirewallModelTests
|
||||
{
|
||||
// ── PveFirewallRule ──────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallRule_Deserialize_Pve9_ReturnsCorrectCount()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_rules.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var rules = data.ToObject<PveFirewallRule[]>();
|
||||
Assert.NotNull(rules);
|
||||
Assert.Equal(3, rules.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallRule_Deserialize_Pve9_FirstRule_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_rules.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var rules = data.ToObject<PveFirewallRule[]>();
|
||||
Assert.NotNull(rules);
|
||||
Assert.Equal(0, rules[0].Pos);
|
||||
Assert.Equal("in", rules[0].Type);
|
||||
Assert.Equal("ACCEPT", rules[0].Action);
|
||||
Assert.Equal("tcp", rules[0].Proto);
|
||||
Assert.Equal("80", rules[0].Dport);
|
||||
Assert.Equal(1, rules[0].Enable);
|
||||
Assert.Equal("Allow HTTP", rules[0].Comment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallRule_Deserialize_Pve9_FirstRule_HasLogLevel()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_rules.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var rules = data.ToObject<PveFirewallRule[]>();
|
||||
Assert.NotNull(rules);
|
||||
Assert.NotNull(rules[0].Log);
|
||||
Assert.Equal("nolog", rules[0].Log);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallRule_Deserialize_Pve9_SecondRule_LogIsNull()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_rules.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var rules = data.ToObject<PveFirewallRule[]>();
|
||||
Assert.NotNull(rules);
|
||||
Assert.Null(rules[1].Log);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallRule_Deserialize_Pve9_ThirdRule_IsOutboundDrop()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_rules.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var rules = data.ToObject<PveFirewallRule[]>();
|
||||
Assert.NotNull(rules);
|
||||
Assert.Equal(2, rules[2].Pos);
|
||||
Assert.Equal("out", rules[2].Type);
|
||||
Assert.Equal("DROP", rules[2].Action);
|
||||
Assert.Equal(0, rules[2].Enable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallRule_Deserialize_Pve9_ThirdRule_NullableFieldsAreNull()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_rules.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var rules = data.ToObject<PveFirewallRule[]>();
|
||||
Assert.NotNull(rules);
|
||||
Assert.Null(rules[2].Proto);
|
||||
Assert.Null(rules[2].Dport);
|
||||
Assert.Null(rules[2].Sport);
|
||||
Assert.Null(rules[2].Source);
|
||||
Assert.Null(rules[2].Dest);
|
||||
Assert.Null(rules[2].Macro);
|
||||
Assert.Null(rules[2].Iface);
|
||||
}
|
||||
|
||||
// ── PveFirewallGroup ────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallGroup_Deserialize_Pve9_ReturnsCorrectCount()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_groups.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var groups = data.ToObject<PveFirewallGroup[]>();
|
||||
Assert.NotNull(groups);
|
||||
Assert.Equal(2, groups.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallGroup_Deserialize_Pve9_FirstGroup_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_groups.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var groups = data.ToObject<PveFirewallGroup[]>();
|
||||
Assert.NotNull(groups);
|
||||
Assert.Equal("webservers", groups[0].Group);
|
||||
Assert.Equal("HTTP/HTTPS servers", groups[0].Comment);
|
||||
Assert.Equal("abc123", groups[0].Digest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallGroup_Deserialize_Pve9_SecondGroup_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_groups.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var groups = data.ToObject<PveFirewallGroup[]>();
|
||||
Assert.NotNull(groups);
|
||||
Assert.Equal("dbservers", groups[1].Group);
|
||||
Assert.Equal("Database servers", groups[1].Comment);
|
||||
Assert.Equal("def456", groups[1].Digest);
|
||||
}
|
||||
|
||||
// ── PveFirewallAlias ────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallAlias_Deserialize_Pve9_ReturnsCorrectCount()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_aliases.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var aliases = data.ToObject<PveFirewallAlias[]>();
|
||||
Assert.NotNull(aliases);
|
||||
Assert.Equal(2, aliases.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallAlias_Deserialize_Pve9_FirstAlias_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_aliases.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var aliases = data.ToObject<PveFirewallAlias[]>();
|
||||
Assert.NotNull(aliases);
|
||||
Assert.Equal("local_network", aliases[0].Name);
|
||||
Assert.Equal("10.0.0.0/8", aliases[0].Cidr);
|
||||
Assert.Equal("RFC1918 private", aliases[0].Comment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallAlias_Deserialize_Pve9_SecondAlias_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_aliases.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var aliases = data.ToObject<PveFirewallAlias[]>();
|
||||
Assert.NotNull(aliases);
|
||||
Assert.Equal("dns_server", aliases[1].Name);
|
||||
Assert.Equal("8.8.8.8", aliases[1].Cidr);
|
||||
Assert.Equal("Google DNS", aliases[1].Comment);
|
||||
}
|
||||
|
||||
// ── PveFirewallIpSet ────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallIpSet_Deserialize_Pve9_ReturnsCorrectCount()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_ipsets.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var ipsets = data.ToObject<PveFirewallIpSet[]>();
|
||||
Assert.NotNull(ipsets);
|
||||
Assert.Equal(2, ipsets.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallIpSet_Deserialize_Pve9_FirstIpSet_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_ipsets.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var ipsets = data.ToObject<PveFirewallIpSet[]>();
|
||||
Assert.NotNull(ipsets);
|
||||
Assert.Equal("blocklist", ipsets[0].Name);
|
||||
Assert.Equal("Blocked IPs", ipsets[0].Comment);
|
||||
Assert.Equal("aaa111", ipsets[0].Digest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallIpSet_Deserialize_Pve9_SecondIpSet_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_ipsets.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var ipsets = data.ToObject<PveFirewallIpSet[]>();
|
||||
Assert.NotNull(ipsets);
|
||||
Assert.Equal("allowlist", ipsets[1].Name);
|
||||
Assert.Equal("Allowed IPs", ipsets[1].Comment);
|
||||
Assert.Equal("bbb222", ipsets[1].Digest);
|
||||
}
|
||||
|
||||
// ── PveFirewallIpSetEntry ───────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallIpSetEntry_Deserialize_Pve9_ReturnsCorrectCount()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_ipset_entries.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var entries = data.ToObject<PveFirewallIpSetEntry[]>();
|
||||
Assert.NotNull(entries);
|
||||
Assert.Equal(2, entries.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallIpSetEntry_Deserialize_Pve9_FirstEntry_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_ipset_entries.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var entries = data.ToObject<PveFirewallIpSetEntry[]>();
|
||||
Assert.NotNull(entries);
|
||||
Assert.Equal("192.168.1.0/24", entries[0].Cidr);
|
||||
Assert.Equal(0, entries[0].NoMatch);
|
||||
Assert.Equal("Local subnet", entries[0].Comment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallIpSetEntry_Deserialize_Pve9_SecondEntry_IsExclusion()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_ipset_entries.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var entries = data.ToObject<PveFirewallIpSetEntry[]>();
|
||||
Assert.NotNull(entries);
|
||||
Assert.Equal("10.0.0.0/8", entries[1].Cidr);
|
||||
Assert.Equal(1, entries[1].NoMatch);
|
||||
Assert.Equal("Exclude private", entries[1].Comment);
|
||||
}
|
||||
|
||||
// ── PveFirewallOptions ──────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallOptions_Deserialize_Pve9_ReturnsNotNull()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_options.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var options = data.ToObject<PveFirewallOptions>();
|
||||
Assert.NotNull(options);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallOptions_Deserialize_Pve9_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_options.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var options = data.ToObject<PveFirewallOptions>();
|
||||
Assert.NotNull(options);
|
||||
Assert.Equal(1, options.Enable);
|
||||
Assert.Equal("DROP", options.PolicyIn);
|
||||
Assert.Equal("ACCEPT", options.PolicyOut);
|
||||
Assert.Equal("nolog", options.LogLevelIn);
|
||||
Assert.Equal("nolog", options.LogLevelOut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallOptions_Deserialize_Pve9_OptionalFieldsAreNull()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_options.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var options = data.ToObject<PveFirewallOptions>();
|
||||
Assert.NotNull(options);
|
||||
Assert.Null(options.Dhcp);
|
||||
Assert.Null(options.Ndp);
|
||||
Assert.Null(options.Radv);
|
||||
Assert.Null(options.MacFilter);
|
||||
Assert.Null(options.IpFilter);
|
||||
}
|
||||
|
||||
// ── PveFirewallRef ──────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallRef_Deserialize_Pve9_ReturnsCorrectCount()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_refs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var refs = data.ToObject<PveFirewallRef[]>();
|
||||
Assert.NotNull(refs);
|
||||
Assert.Equal(2, refs.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallRef_Deserialize_Pve9_FirstRef_IsAlias()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_refs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var refs = data.ToObject<PveFirewallRef[]>();
|
||||
Assert.NotNull(refs);
|
||||
Assert.Equal("alias", refs[0].Type);
|
||||
Assert.Equal("local_network", refs[0].Name);
|
||||
Assert.Equal("RFC1918 private", refs[0].Comment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveFirewallRef_Deserialize_Pve9_SecondRef_IsIpSet()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_firewall_refs.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var refs = data.ToObject<PveFirewallRef[]>();
|
||||
Assert.NotNull(refs);
|
||||
Assert.Equal("ipset", refs[1].Type);
|
||||
Assert.Equal("blocklist", refs[1].Name);
|
||||
Assert.Equal("Blocked IPs", refs[1].Comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,5 +142,125 @@ namespace PSProxmoxVE.Core.Tests.Models
|
||||
// Comments not present in fixture entries
|
||||
Assert.Null(vnets[0].Comments);
|
||||
}
|
||||
|
||||
// ── PveSdnIpam ─────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void PveSdnIpam_Deserialize_Pve9_ReturnsCorrectCount()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_sdn_ipams.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var ipams = data.ToObject<PveSdnIpam[]>();
|
||||
Assert.NotNull(ipams);
|
||||
Assert.Equal(2, ipams.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveSdnIpam_Deserialize_Pve9_FirstIpam_IsPveType()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_sdn_ipams.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var ipams = data.ToObject<PveSdnIpam[]>();
|
||||
Assert.NotNull(ipams);
|
||||
Assert.Equal("pve", ipams[0].Ipam);
|
||||
Assert.Equal("pve", ipams[0].Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveSdnIpam_Deserialize_Pve9_FirstIpam_OptionalFieldsAreNull()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_sdn_ipams.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var ipams = data.ToObject<PveSdnIpam[]>();
|
||||
Assert.NotNull(ipams);
|
||||
Assert.Null(ipams[0].Url);
|
||||
Assert.Null(ipams[0].Token);
|
||||
Assert.Null(ipams[0].Section);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveSdnIpam_Deserialize_Pve9_SecondIpam_HasNetboxProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_sdn_ipams.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var ipams = data.ToObject<PveSdnIpam[]>();
|
||||
Assert.NotNull(ipams);
|
||||
Assert.Equal("netbox1", ipams[1].Ipam);
|
||||
Assert.Equal("netbox", ipams[1].Type);
|
||||
Assert.Equal("https://netbox.example.com", ipams[1].Url);
|
||||
Assert.Equal("abc123", ipams[1].Token);
|
||||
Assert.Equal(1, ipams[1].Section);
|
||||
}
|
||||
|
||||
// ── PveSdnDns ──────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void PveSdnDns_Deserialize_Pve9_ReturnsCorrectCount()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_sdn_dns.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var dnsEntries = data.ToObject<PveSdnDns[]>();
|
||||
Assert.NotNull(dnsEntries);
|
||||
Assert.Single(dnsEntries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveSdnDns_Deserialize_Pve9_FirstEntry_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_sdn_dns.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var dnsEntries = data.ToObject<PveSdnDns[]>();
|
||||
Assert.NotNull(dnsEntries);
|
||||
Assert.Equal("powerdns1", dnsEntries[0].Dns);
|
||||
Assert.Equal("powerdns", dnsEntries[0].Type);
|
||||
Assert.Equal("http://dns.example.com:8081", dnsEntries[0].Url);
|
||||
Assert.Equal("secret123", dnsEntries[0].Key);
|
||||
Assert.Equal(3600, dnsEntries[0].Ttl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveSdnDns_Deserialize_Pve9_FirstEntry_OptionalFieldsAreNull()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_sdn_dns.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var dnsEntries = data.ToObject<PveSdnDns[]>();
|
||||
Assert.NotNull(dnsEntries);
|
||||
Assert.Null(dnsEntries[0].ReverseMaskV6);
|
||||
}
|
||||
|
||||
// ── PveSdnController ───────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void PveSdnController_Deserialize_Pve9_ReturnsCorrectCount()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_sdn_controllers.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var controllers = data.ToObject<PveSdnController[]>();
|
||||
Assert.NotNull(controllers);
|
||||
Assert.Single(controllers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PveSdnController_Deserialize_Pve9_FirstController_HasCorrectProperties()
|
||||
{
|
||||
var json = TestHelper.LoadFixture("pve9_sdn_controllers.json");
|
||||
var data = JObject.Parse(json)["data"];
|
||||
Assert.NotNull(data);
|
||||
var controllers = data.ToObject<PveSdnController[]>();
|
||||
Assert.NotNull(controllers);
|
||||
Assert.Equal("evpn1", controllers[0].Controller);
|
||||
Assert.Equal("evpn", controllers[0].Type);
|
||||
Assert.Equal(65000, controllers[0].Asn);
|
||||
Assert.Equal("10.0.0.1,10.0.0.2", controllers[0].Peers);
|
||||
Assert.Equal("pve1", controllers[0].Node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for backup cmdlets:
|
||||
New-PveBackup, Get-PveBackupJob, New-PveBackupJob, Set-PveBackupJob, Remove-PveBackupJob.
|
||||
|
||||
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 @('New-PveBackup', 'Get-PveBackupJob', 'New-PveBackupJob', 'Set-PveBackupJob', 'Remove-PveBackupJob')) {
|
||||
$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 'Backup 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 = 'New-PveBackup' }
|
||||
@{ cmdName = 'Get-PveBackupJob' }
|
||||
@{ cmdName = 'New-PveBackupJob' }
|
||||
@{ cmdName = 'Set-PveBackupJob' }
|
||||
@{ cmdName = 'Remove-PveBackupJob' }
|
||||
) {
|
||||
if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return }
|
||||
$script:Manifest.CmdletsToExport | Should -Contain $cmdName
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveBackup
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveBackup' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveBackup' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveBackup'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'New-PveBackup'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveBackup'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have VmId parameter' {
|
||||
Skip-IfMissing 'New-PveBackup'
|
||||
$script:Cmd.Parameters.ContainsKey('VmId') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Storage parameter' {
|
||||
Skip-IfMissing 'New-PveBackup'
|
||||
$script:Cmd.Parameters.ContainsKey('Storage') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Node parameter' {
|
||||
Skip-IfMissing 'New-PveBackup'
|
||||
$script:Cmd.Parameters.ContainsKey('Node') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'New-PveBackup'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PveBackup'
|
||||
{ New-PveBackup -VmId 100 -Storage 'local' -Node 'pve' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveBackupJob
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveBackupJob' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveBackupJob' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveBackupJob'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveBackupJob'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional Id parameter' {
|
||||
Skip-IfMissing 'Get-PveBackupJob'
|
||||
$script:Cmd.Parameters.ContainsKey('Id') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveBackupJob'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveBackupJob'
|
||||
{ Get-PveBackupJob -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveBackupJob
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveBackupJob' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveBackupJob' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveBackupJob'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveBackupJob'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Storage should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveBackupJob'
|
||||
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Schedule should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveBackupJob'
|
||||
$isMandatory = $script:Cmd.Parameters['Schedule'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Optional parameters' {
|
||||
It 'Should have VmId parameter' {
|
||||
Skip-IfMissing 'New-PveBackupJob'
|
||||
$script:Cmd.Parameters.ContainsKey('VmId') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Mode parameter' {
|
||||
Skip-IfMissing 'New-PveBackupJob'
|
||||
$script:Cmd.Parameters.ContainsKey('Mode') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Compress parameter' {
|
||||
Skip-IfMissing 'New-PveBackupJob'
|
||||
$script:Cmd.Parameters.ContainsKey('Compress') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PveBackupJob'
|
||||
{ New-PveBackupJob -Storage 'local' -Schedule 'daily' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveBackupJob
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveBackupJob' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveBackupJob' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveBackupJob'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveBackupJob'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Id should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveBackupJob'
|
||||
$isMandatory = $script:Cmd.Parameters['Id'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveBackupJob'
|
||||
{ Set-PveBackupJob -Id 'backup-test' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveBackupJob
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveBackupJob' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveBackupJob' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveBackupJob'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveBackupJob'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveBackupJob'
|
||||
$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 'Id should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveBackupJob'
|
||||
$isMandatory = $script:Cmd.Parameters['Id'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Remove-PveBackupJob'
|
||||
{ Remove-PveBackupJob -Id 'backup-test' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for firewall alias cmdlets:
|
||||
Get-PveFirewallAlias, New-PveFirewallAlias, Set-PveFirewallAlias, Remove-PveFirewallAlias.
|
||||
|
||||
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-PveFirewallAlias', 'New-PveFirewallAlias', 'Set-PveFirewallAlias', 'Remove-PveFirewallAlias')) {
|
||||
$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 'Firewall alias 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-PveFirewallAlias' }
|
||||
@{ cmdName = 'New-PveFirewallAlias' }
|
||||
@{ cmdName = 'Set-PveFirewallAlias' }
|
||||
@{ cmdName = 'Remove-PveFirewallAlias' }
|
||||
) {
|
||||
if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return }
|
||||
$script:Manifest.CmdletsToExport | Should -Contain $cmdName
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveFirewallAlias
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveFirewallAlias' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveFirewallAlias' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveFirewallAlias'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveFirewallAlias'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional Name parameter' {
|
||||
Skip-IfMissing 'Get-PveFirewallAlias'
|
||||
$script:Cmd.Parameters.ContainsKey('Name') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveFirewallAlias'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveFirewallAlias'
|
||||
{ Get-PveFirewallAlias -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveFirewallAlias
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveFirewallAlias' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveFirewallAlias' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveFirewallAlias'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveFirewallAlias'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Name should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveFirewallAlias'
|
||||
$isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Cidr should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveFirewallAlias'
|
||||
$isMandatory = $script:Cmd.Parameters['Cidr'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Optional parameters' {
|
||||
It 'Should have Comment parameter' {
|
||||
Skip-IfMissing 'New-PveFirewallAlias'
|
||||
$script:Cmd.Parameters.ContainsKey('Comment') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PveFirewallAlias'
|
||||
{ New-PveFirewallAlias -Name 'testalias' -Cidr '10.0.0.0/24' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveFirewallAlias
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveFirewallAlias' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveFirewallAlias' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveFirewallAlias'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveFirewallAlias'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Name should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveFirewallAlias'
|
||||
$isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Cidr should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveFirewallAlias'
|
||||
$isMandatory = $script:Cmd.Parameters['Cidr'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveFirewallAlias'
|
||||
{ Set-PveFirewallAlias -Name 'testalias' -Cidr '10.0.0.0/24' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveFirewallAlias
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveFirewallAlias' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveFirewallAlias' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveFirewallAlias'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveFirewallAlias'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveFirewallAlias'
|
||||
$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 'Name should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveFirewallAlias'
|
||||
$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' {
|
||||
Skip-IfMissing 'Remove-PveFirewallAlias'
|
||||
{ Remove-PveFirewallAlias -Name 'testalias' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for firewall group cmdlets:
|
||||
Get-PveFirewallGroup, New-PveFirewallGroup, Remove-PveFirewallGroup.
|
||||
|
||||
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-PveFirewallGroup', 'New-PveFirewallGroup', 'Remove-PveFirewallGroup')) {
|
||||
$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 'Firewall group 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-PveFirewallGroup' }
|
||||
@{ cmdName = 'New-PveFirewallGroup' }
|
||||
@{ cmdName = 'Remove-PveFirewallGroup' }
|
||||
) {
|
||||
if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return }
|
||||
$script:Manifest.CmdletsToExport | Should -Contain $cmdName
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveFirewallGroup
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveFirewallGroup' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveFirewallGroup' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveFirewallGroup'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveFirewallGroup'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional Group parameter' {
|
||||
Skip-IfMissing 'Get-PveFirewallGroup'
|
||||
$script:Cmd.Parameters.ContainsKey('Group') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveFirewallGroup'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveFirewallGroup'
|
||||
{ Get-PveFirewallGroup -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveFirewallGroup
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveFirewallGroup' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveFirewallGroup' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveFirewallGroup'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveFirewallGroup'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Group should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveFirewallGroup'
|
||||
$isMandatory = $script:Cmd.Parameters['Group'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Optional parameters' {
|
||||
It 'Should have Comment parameter' {
|
||||
Skip-IfMissing 'New-PveFirewallGroup'
|
||||
$script:Cmd.Parameters.ContainsKey('Comment') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PveFirewallGroup'
|
||||
{ New-PveFirewallGroup -Group 'testgroup' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveFirewallGroup
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveFirewallGroup' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveFirewallGroup' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveFirewallGroup'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveFirewallGroup'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveFirewallGroup'
|
||||
$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 'Group should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveFirewallGroup'
|
||||
$isMandatory = $script:Cmd.Parameters['Group'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Remove-PveFirewallGroup'
|
||||
{ Remove-PveFirewallGroup -Group 'testgroup' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for firewall IP set cmdlets:
|
||||
Get-PveFirewallIpSet, New-PveFirewallIpSet, Remove-PveFirewallIpSet,
|
||||
Get-PveFirewallIpSetEntry, New-PveFirewallIpSetEntry, Set-PveFirewallIpSetEntry,
|
||||
Remove-PveFirewallIpSetEntry.
|
||||
|
||||
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-PveFirewallIpSet', 'New-PveFirewallIpSet', 'Remove-PveFirewallIpSet',
|
||||
'Get-PveFirewallIpSetEntry', 'New-PveFirewallIpSetEntry', 'Set-PveFirewallIpSetEntry',
|
||||
'Remove-PveFirewallIpSetEntry'
|
||||
)) {
|
||||
$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 'Firewall IP set 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-PveFirewallIpSet' }
|
||||
@{ cmdName = 'New-PveFirewallIpSet' }
|
||||
@{ cmdName = 'Remove-PveFirewallIpSet' }
|
||||
@{ cmdName = 'Get-PveFirewallIpSetEntry' }
|
||||
@{ cmdName = 'New-PveFirewallIpSetEntry' }
|
||||
@{ cmdName = 'Set-PveFirewallIpSetEntry' }
|
||||
@{ cmdName = 'Remove-PveFirewallIpSetEntry' }
|
||||
) {
|
||||
if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return }
|
||||
$script:Manifest.CmdletsToExport | Should -Contain $cmdName
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveFirewallIpSet
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveFirewallIpSet' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveFirewallIpSet' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveFirewallIpSet'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveFirewallIpSet'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional Name parameter' {
|
||||
Skip-IfMissing 'Get-PveFirewallIpSet'
|
||||
$script:Cmd.Parameters.ContainsKey('Name') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveFirewallIpSet'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveFirewallIpSet'
|
||||
{ Get-PveFirewallIpSet -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveFirewallIpSet
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveFirewallIpSet' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveFirewallIpSet' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveFirewallIpSet'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveFirewallIpSet'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Name should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveFirewallIpSet'
|
||||
$isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Optional parameters' {
|
||||
It 'Should have Comment parameter' {
|
||||
Skip-IfMissing 'New-PveFirewallIpSet'
|
||||
$script:Cmd.Parameters.ContainsKey('Comment') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PveFirewallIpSet'
|
||||
{ New-PveFirewallIpSet -Name 'testipset' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveFirewallIpSet
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveFirewallIpSet' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveFirewallIpSet' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveFirewallIpSet'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveFirewallIpSet'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveFirewallIpSet'
|
||||
$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 'Name should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveFirewallIpSet'
|
||||
$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' {
|
||||
Skip-IfMissing 'Remove-PveFirewallIpSet'
|
||||
{ Remove-PveFirewallIpSet -Name 'testipset' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveFirewallIpSetEntry
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveFirewallIpSetEntry' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveFirewallIpSetEntry' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveFirewallIpSetEntry'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveFirewallIpSetEntry'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Name should be Mandatory' {
|
||||
Skip-IfMissing 'Get-PveFirewallIpSetEntry'
|
||||
$isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveFirewallIpSetEntry'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveFirewallIpSetEntry'
|
||||
{ Get-PveFirewallIpSetEntry -Name 'testipset' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveFirewallIpSetEntry
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveFirewallIpSetEntry' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveFirewallIpSetEntry' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveFirewallIpSetEntry'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveFirewallIpSetEntry'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Name should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveFirewallIpSetEntry'
|
||||
$isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Cidr should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveFirewallIpSetEntry'
|
||||
$isMandatory = $script:Cmd.Parameters['Cidr'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PveFirewallIpSetEntry'
|
||||
{ New-PveFirewallIpSetEntry -Name 'testipset' -Cidr '10.0.0.1' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveFirewallIpSetEntry
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveFirewallIpSetEntry' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveFirewallIpSetEntry' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveFirewallIpSetEntry'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveFirewallIpSetEntry'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Name should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveFirewallIpSetEntry'
|
||||
$isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Cidr should be Mandatory' {
|
||||
Skip-IfMissing 'Set-PveFirewallIpSetEntry'
|
||||
$isMandatory = $script:Cmd.Parameters['Cidr'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveFirewallIpSetEntry'
|
||||
{ Set-PveFirewallIpSetEntry -Name 'testipset' -Cidr '10.0.0.1' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveFirewallIpSetEntry
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveFirewallIpSetEntry' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveFirewallIpSetEntry' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveFirewallIpSetEntry'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveFirewallIpSetEntry'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveFirewallIpSetEntry'
|
||||
$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 'Name should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveFirewallIpSetEntry'
|
||||
$isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Cidr should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-PveFirewallIpSetEntry'
|
||||
$isMandatory = $script:Cmd.Parameters['Cidr'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Remove-PveFirewallIpSetEntry'
|
||||
{ Remove-PveFirewallIpSetEntry -Name 'testipset' -Cidr '10.0.0.1' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for firewall options/ref cmdlets:
|
||||
Get-PveFirewallOptions, Set-PveFirewallOptions, Get-PveFirewallRef.
|
||||
|
||||
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-PveFirewallOptions', 'Set-PveFirewallOptions', 'Get-PveFirewallRef')) {
|
||||
$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 'Firewall options/ref 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-PveFirewallOptions' }
|
||||
@{ cmdName = 'Set-PveFirewallOptions' }
|
||||
@{ cmdName = 'Get-PveFirewallRef' }
|
||||
) {
|
||||
if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return }
|
||||
$script:Manifest.CmdletsToExport | Should -Contain $cmdName
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveFirewallOptions
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveFirewallOptions' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveFirewallOptions' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveFirewallOptions'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveFirewallOptions'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have mandatory Level parameter with ValidateSet' {
|
||||
Skip-IfMissing 'Get-PveFirewallOptions'
|
||||
$param = $script:Cmd.Parameters['Level']
|
||||
$param | Should -Not -BeNullOrEmpty
|
||||
$isMandatory = $param.ParameterSets.Values | Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
$validateSet = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] }
|
||||
$validateSet | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveFirewallOptions'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveFirewallOptions'
|
||||
{ Get-PveFirewallOptions -Level 'cluster' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveFirewallOptions
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveFirewallOptions' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveFirewallOptions' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveFirewallOptions'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveFirewallOptions'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have mandatory Level parameter with ValidateSet' {
|
||||
Skip-IfMissing 'Set-PveFirewallOptions'
|
||||
$param = $script:Cmd.Parameters['Level']
|
||||
$param | Should -Not -BeNullOrEmpty
|
||||
$isMandatory = $param.ParameterSets.Values | Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
$validateSet = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] }
|
||||
$validateSet | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should have Enable parameter' {
|
||||
Skip-IfMissing 'Set-PveFirewallOptions'
|
||||
$script:Cmd.Parameters.ContainsKey('Enable') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveFirewallOptions'
|
||||
{ Set-PveFirewallOptions -Level 'cluster' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveFirewallRef
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveFirewallRef' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveFirewallRef' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveFirewallRef'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveFirewallRef'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveFirewallRef'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have optional Type parameter' {
|
||||
Skip-IfMissing 'Get-PveFirewallRef'
|
||||
$script:Cmd.Parameters.ContainsKey('Type') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveFirewallRef'
|
||||
{ Get-PveFirewallRef -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for firewall rule cmdlets:
|
||||
Get-PveFirewallRule, New-PveFirewallRule, Set-PveFirewallRule, Remove-PveFirewallRule.
|
||||
|
||||
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-PveFirewallRule', 'New-PveFirewallRule', 'Set-PveFirewallRule', 'Remove-PveFirewallRule')) {
|
||||
$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 'Firewall rule 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-PveFirewallRule' }
|
||||
@{ cmdName = 'New-PveFirewallRule' }
|
||||
@{ cmdName = 'Set-PveFirewallRule' }
|
||||
@{ cmdName = 'Remove-PveFirewallRule' }
|
||||
) {
|
||||
if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return }
|
||||
$script:Manifest.CmdletsToExport | Should -Contain $cmdName
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveFirewallRule
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveFirewallRule' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveFirewallRule' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveFirewallRule'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveFirewallRule'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have mandatory Level parameter with ValidateSet' {
|
||||
Skip-IfMissing 'Get-PveFirewallRule'
|
||||
$param = $script:Cmd.Parameters['Level']
|
||||
$param | Should -Not -BeNullOrEmpty
|
||||
$isMandatory = $param.ParameterSets.Values | Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
$validateSet = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] }
|
||||
$validateSet | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveFirewallRule'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveFirewallRule'
|
||||
{ Get-PveFirewallRule -Level 'cluster' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveFirewallRule
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveFirewallRule' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveFirewallRule' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveFirewallRule'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveFirewallRule'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have mandatory Level parameter with ValidateSet' {
|
||||
Skip-IfMissing 'New-PveFirewallRule'
|
||||
$param = $script:Cmd.Parameters['Level']
|
||||
$param | Should -Not -BeNullOrEmpty
|
||||
$isMandatory = $param.ParameterSets.Values | Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
$validateSet = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] }
|
||||
$validateSet | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should have Action parameter' {
|
||||
Skip-IfMissing 'New-PveFirewallRule'
|
||||
$script:Cmd.Parameters.ContainsKey('Action') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Type parameter' {
|
||||
Skip-IfMissing 'New-PveFirewallRule'
|
||||
$script:Cmd.Parameters.ContainsKey('Type') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'New-PveFirewallRule'
|
||||
{ New-PveFirewallRule -Level 'cluster' -Action 'ACCEPT' -Type 'in' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Set-PveFirewallRule
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Set-PveFirewallRule' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Set-PveFirewallRule' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Set-PveFirewallRule'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Set-PveFirewallRule'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have mandatory Level parameter with ValidateSet' {
|
||||
Skip-IfMissing 'Set-PveFirewallRule'
|
||||
$param = $script:Cmd.Parameters['Level']
|
||||
$param | Should -Not -BeNullOrEmpty
|
||||
$isMandatory = $param.ParameterSets.Values | Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
$validateSet = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] }
|
||||
$validateSet | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should have Pos parameter' {
|
||||
Skip-IfMissing 'Set-PveFirewallRule'
|
||||
$script:Cmd.Parameters.ContainsKey('Pos') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Set-PveFirewallRule'
|
||||
{ Set-PveFirewallRule -Level 'cluster' -Pos 0 -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveFirewallRule
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveFirewallRule' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveFirewallRule' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveFirewallRule'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveFirewallRule'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveFirewallRule'
|
||||
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
|
||||
[System.Management.Automation.CmdletAttribute], $false) |
|
||||
Select-Object -First 1
|
||||
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have mandatory Level parameter with ValidateSet' {
|
||||
Skip-IfMissing 'Remove-PveFirewallRule'
|
||||
$param = $script:Cmd.Parameters['Level']
|
||||
$param | Should -Not -BeNullOrEmpty
|
||||
$isMandatory = $param.ParameterSets.Values | Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
$validateSet = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] }
|
||||
$validateSet | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should have Pos parameter' {
|
||||
Skip-IfMissing 'Remove-PveFirewallRule'
|
||||
$script:Cmd.Parameters.ContainsKey('Pos') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Remove-PveFirewallRule'
|
||||
{ Remove-PveFirewallRule -Level 'cluster' -Pos 0 -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,8 @@ BeforeAll {
|
||||
$script:CreatedRoles = [System.Collections.Generic.List[string]]::new()
|
||||
$script:TestVmId = $null
|
||||
$script:TestContainerId = $null
|
||||
$script:FirewallTestRulePos = $null
|
||||
$script:BackupTestJobId = $null
|
||||
|
||||
# Helper functions for skip logic
|
||||
function script:Skip-IfNoTarget {
|
||||
@@ -159,6 +161,32 @@ AfterAll {
|
||||
}
|
||||
catch { <# non-fatal #> }
|
||||
}
|
||||
|
||||
# Cleanup: firewall test artifacts
|
||||
try {
|
||||
$rules = Get-PveFirewallRule -Level Cluster -ErrorAction SilentlyContinue
|
||||
$testRules = $rules | Where-Object { $_.Comment -like 'pester-test*' }
|
||||
foreach ($r in $testRules) {
|
||||
Remove-PveFirewallRule -Level Cluster -Position $r.Pos -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
|
||||
# Cleanup: firewall aliases
|
||||
try {
|
||||
Remove-PveFirewallAlias -Level Cluster -Name 'pester-alias' -Confirm:$false -ErrorAction SilentlyContinue
|
||||
} catch { }
|
||||
|
||||
# Cleanup: firewall IP sets
|
||||
try {
|
||||
Remove-PveFirewallIpSet -Level Cluster -Name 'pester-ipset' -Confirm:$false -ErrorAction SilentlyContinue
|
||||
} catch { }
|
||||
|
||||
# Cleanup: backup jobs
|
||||
try {
|
||||
if ($script:BackupTestJobId) {
|
||||
Remove-PveBackupJob -Id $script:BackupTestJobId -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
@@ -1343,6 +1371,161 @@ Describe 'Integration Tests' -Tag 'Integration' {
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
Context 'OVA Import' {
|
||||
BeforeAll {
|
||||
function Skip-IfNoOva {
|
||||
if (-not $script:OvaPath) {
|
||||
Set-ItResult -Skipped -Because 'PVETEST_OVA_PATH is not set'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It 'Should import a VM from OVA file' {
|
||||
Skip-IfNoOva
|
||||
$vmId = 9900 + (Get-Random -Maximum 99)
|
||||
$result = Import-PveOva -Node $script:Node -Path $script:OvaPath -Storage $script:Storage -NewVmId $vmId -ErrorAction Stop
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$script:CreatedVmIds.Add($vmId) | Out-Null
|
||||
}
|
||||
|
||||
It 'Should find the imported VM' {
|
||||
Skip-IfNoOva
|
||||
if ($script:CreatedVmIds.Count -eq 0 -or $script:CreatedVmIds[-1] -lt 9900) {
|
||||
Set-ItResult -Skipped -Because 'OVA import did not create a VM'
|
||||
}
|
||||
$vm = Get-PveVm -Node $script:Node | Where-Object { $_.VmId -eq $script:CreatedVmIds[-1] }
|
||||
$vm | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
Context 'Firewall — Cluster Rules' {
|
||||
It 'Should create a firewall rule' {
|
||||
Skip-IfNoTarget
|
||||
{ New-PveFirewallRule -Level Cluster -Type in -Action ACCEPT -Proto tcp -Dport '8080' -Comment 'pester-test-rule' -Enable -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list firewall rules and find the test rule' {
|
||||
Skip-IfNoTarget
|
||||
$rules = Get-PveFirewallRule -Level Cluster
|
||||
$rules | Should -Not -BeNullOrEmpty
|
||||
$testRule = $rules | Where-Object { $_.Comment -eq 'pester-test-rule' }
|
||||
$testRule | Should -Not -BeNullOrEmpty
|
||||
$script:FirewallTestRulePos = $testRule.Pos
|
||||
}
|
||||
|
||||
It 'Should update the firewall rule' {
|
||||
Skip-IfNoTarget
|
||||
if ($null -eq $script:FirewallTestRulePos) {
|
||||
Set-ItResult -Skipped -Because 'No test rule was created'
|
||||
}
|
||||
{ Set-PveFirewallRule -Level Cluster -Position $script:FirewallTestRulePos -Comment 'pester-test-updated' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should remove the firewall rule' {
|
||||
Skip-IfNoTarget
|
||||
if ($null -eq $script:FirewallTestRulePos) {
|
||||
Set-ItResult -Skipped -Because 'No test rule was created'
|
||||
}
|
||||
{ Remove-PveFirewallRule -Level Cluster -Position $script:FirewallTestRulePos -Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should get firewall options' {
|
||||
Skip-IfNoTarget
|
||||
$opts = Get-PveFirewallOptions -Level Cluster
|
||||
$opts | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
Context 'Backup Jobs' {
|
||||
It 'Should create a backup job' {
|
||||
Skip-IfNoTarget
|
||||
{ New-PveBackupJob -Schedule '0 3 * * 6' -Storage $script:Storage -Mode snapshot -Comment 'pester-test-backup' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list backup jobs and find the test job' {
|
||||
Skip-IfNoTarget
|
||||
$jobs = Get-PveBackupJob
|
||||
$testJob = $jobs | Where-Object { $_.Comment -eq 'pester-test-backup' }
|
||||
$testJob | Should -Not -BeNullOrEmpty
|
||||
$script:BackupTestJobId = $testJob.Id
|
||||
}
|
||||
|
||||
It 'Should update the backup job' {
|
||||
Skip-IfNoTarget
|
||||
if (-not $script:BackupTestJobId) {
|
||||
Set-ItResult -Skipped -Because 'No test backup job was created'
|
||||
}
|
||||
{ Set-PveBackupJob -Id $script:BackupTestJobId -Comment 'pester-test-updated' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should remove the backup job' {
|
||||
Skip-IfNoTarget
|
||||
if (-not $script:BackupTestJobId) {
|
||||
Set-ItResult -Skipped -Because 'No test backup job was created'
|
||||
}
|
||||
{ Remove-PveBackupJob -Id $script:BackupTestJobId -Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should verify the backup job was removed' {
|
||||
Skip-IfNoTarget
|
||||
if (-not $script:BackupTestJobId) {
|
||||
Set-ItResult -Skipped -Because 'No test backup job was created'
|
||||
}
|
||||
$jobs = Get-PveBackupJob
|
||||
$testJob = $jobs | Where-Object { $_.Id -eq $script:BackupTestJobId }
|
||||
$testJob | Should -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
Context 'Firewall — Aliases and IP Sets' {
|
||||
It 'Should create a firewall alias' {
|
||||
Skip-IfNoTarget
|
||||
{ New-PveFirewallAlias -Level Cluster -Name 'pester-alias' -Cidr '192.168.99.0/24' -Comment 'test alias' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list and find the alias' {
|
||||
Skip-IfNoTarget
|
||||
$aliases = Get-PveFirewallAlias -Level Cluster
|
||||
$aliases | Where-Object { $_.Name -eq 'pester-alias' } | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should remove the alias' {
|
||||
Skip-IfNoTarget
|
||||
{ Remove-PveFirewallAlias -Level Cluster -Name 'pester-alias' -Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should create an IP set' {
|
||||
Skip-IfNoTarget
|
||||
{ New-PveFirewallIpSet -Level Cluster -Name 'pester-ipset' -Comment 'test ipset' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should add an entry to the IP set' {
|
||||
Skip-IfNoTarget
|
||||
{ New-PveFirewallIpSetEntry -Level Cluster -Name 'pester-ipset' -Cidr '10.99.0.0/16' -Comment 'test entry' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list IP set entries' {
|
||||
Skip-IfNoTarget
|
||||
$entries = Get-PveFirewallIpSetEntry -Level Cluster -Name 'pester-ipset'
|
||||
$entries | Should -Not -BeNullOrEmpty
|
||||
($entries | Where-Object { $_.Cidr -like '10.99.0.0*' }) | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should remove the IP set entry' {
|
||||
Skip-IfNoTarget
|
||||
{ Remove-PveFirewallIpSetEntry -Level Cluster -Name 'pester-ipset' -Cidr '10.99.0.0/16' -Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should remove the IP set' {
|
||||
Skip-IfNoTarget
|
||||
{ Remove-PveFirewallIpSet -Level Cluster -Name 'pester-ipset' -Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
Context 'Disconnect' {
|
||||
It 'Should disconnect from PVE server (Disconnect-PveServer)' {
|
||||
|
||||
@@ -0,0 +1,466 @@
|
||||
#Requires -Module Pester
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pester 5 tests for SDN IPAM, DNS, and controller cmdlets:
|
||||
Get-PveSdnIpam, New-PveSdnIpam, Remove-PveSdnIpam,
|
||||
Get-PveSdnDns, New-PveSdnDns, Remove-PveSdnDns,
|
||||
Get-PveSdnController, New-PveSdnController, Remove-PveSdnController.
|
||||
|
||||
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-PveSdnIpam', 'New-PveSdnIpam', 'Remove-PveSdnIpam',
|
||||
'Get-PveSdnDns', 'New-PveSdnDns', 'Remove-PveSdnDns',
|
||||
'Get-PveSdnController', 'New-PveSdnController', 'Remove-PveSdnController'
|
||||
)) {
|
||||
$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 IPAM/DNS/Controller 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-PveSdnIpam' }
|
||||
@{ cmdName = 'New-PveSdnIpam' }
|
||||
@{ cmdName = 'Remove-PveSdnIpam' }
|
||||
@{ cmdName = 'Get-PveSdnDns' }
|
||||
@{ cmdName = 'New-PveSdnDns' }
|
||||
@{ cmdName = 'Remove-PveSdnDns' }
|
||||
@{ cmdName = 'Get-PveSdnController' }
|
||||
@{ cmdName = 'New-PveSdnController' }
|
||||
@{ cmdName = 'Remove-PveSdnController' }
|
||||
) {
|
||||
if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return }
|
||||
$script:Manifest.CmdletsToExport | Should -Contain $cmdName
|
||||
}
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
# IPAM cmdlets
|
||||
# ===========================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveSdnIpam
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveSdnIpam' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveSdnIpam' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveSdnIpam'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveSdnIpam'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional Ipam parameter' {
|
||||
Skip-IfMissing 'Get-PveSdnIpam'
|
||||
$script:Cmd.Parameters.ContainsKey('Ipam') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveSdnIpam'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveSdnIpam'
|
||||
{ Get-PveSdnIpam -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveSdnIpam
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveSdnIpam' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveSdnIpam' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveSdnIpam'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveSdnIpam'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Ipam should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveSdnIpam'
|
||||
$isMandatory = $script:Cmd.Parameters['Ipam'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Type should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveSdnIpam'
|
||||
$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-PveSdnIpam'
|
||||
{ New-PveSdnIpam -Ipam 'testipam' -Type 'pve' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveSdnIpam
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveSdnIpam' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveSdnIpam' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveSdnIpam'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveSdnIpam'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveSdnIpam'
|
||||
$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 'Ipam should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-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 'Remove-PveSdnIpam'
|
||||
{ Remove-PveSdnIpam -Ipam 'testipam' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
# DNS cmdlets
|
||||
# ===========================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveSdnDns
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveSdnDns' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveSdnDns' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveSdnDns'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveSdnDns'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional Dns parameter' {
|
||||
Skip-IfMissing 'Get-PveSdnDns'
|
||||
$script:Cmd.Parameters.ContainsKey('Dns') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveSdnDns'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveSdnDns'
|
||||
{ Get-PveSdnDns -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveSdnDns
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveSdnDns' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveSdnDns' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveSdnDns'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveSdnDns'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Dns should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveSdnDns'
|
||||
$isMandatory = $script:Cmd.Parameters['Dns'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Type should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveSdnDns'
|
||||
$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-PveSdnDns'
|
||||
{ New-PveSdnDns -Dns 'testdns' -Type 'powerdns' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveSdnDns
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveSdnDns' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveSdnDns' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveSdnDns'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveSdnDns'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveSdnDns'
|
||||
$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 'Dns should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-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 'Remove-PveSdnDns'
|
||||
{ Remove-PveSdnDns -Dns 'testdns' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
# Controller cmdlets
|
||||
# ===========================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Get-PveSdnController
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Get-PveSdnController' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Get-PveSdnController' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Get-PveSdnController'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should be a CmdletInfo (binary cmdlet)' {
|
||||
Skip-IfMissing 'Get-PveSdnController'
|
||||
$script:Cmd.CommandType | Should -Be 'Cmdlet'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Parameter metadata' {
|
||||
It 'Should have optional Controller parameter' {
|
||||
Skip-IfMissing 'Get-PveSdnController'
|
||||
$script:Cmd.Parameters.ContainsKey('Controller') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should have Session parameter' {
|
||||
Skip-IfMissing 'Get-PveSdnController'
|
||||
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Without active session' {
|
||||
It 'Should throw when no session is active' {
|
||||
Skip-IfMissing 'Get-PveSdnController'
|
||||
{ Get-PveSdnController -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# New-PveSdnController
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'New-PveSdnController' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'New-PveSdnController' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'New-PveSdnController'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess support' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'New-PveSdnController'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Required parameters' {
|
||||
It 'Controller should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveSdnController'
|
||||
$isMandatory = $script:Cmd.Parameters['Controller'].ParameterSets.Values |
|
||||
Where-Object { $_.IsMandatory }
|
||||
$isMandatory | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Type should be Mandatory' {
|
||||
Skip-IfMissing 'New-PveSdnController'
|
||||
$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-PveSdnController'
|
||||
{ New-PveSdnController -Controller 'testctrl' -Type 'evpn' -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Remove-PveSdnController
|
||||
# ---------------------------------------------------------------------------
|
||||
Describe 'Remove-PveSdnController' {
|
||||
|
||||
BeforeAll { $script:Cmd = Get-Command 'Remove-PveSdnController' -ErrorAction SilentlyContinue }
|
||||
|
||||
Context 'Command existence' {
|
||||
It 'Should be available after module import' {
|
||||
Skip-IfMissing 'Remove-PveSdnController'
|
||||
$script:Cmd | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'ShouldProcess / ConfirmImpact' {
|
||||
It 'Should support WhatIf' {
|
||||
Skip-IfMissing 'Remove-PveSdnController'
|
||||
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should declare ConfirmImpact High' {
|
||||
Skip-IfMissing 'Remove-PveSdnController'
|
||||
$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 'Controller should be Mandatory' {
|
||||
Skip-IfMissing 'Remove-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 'Remove-PveSdnController'
|
||||
{ Remove-PveSdnController -Controller 'testctrl' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Throw '*No active Proxmox VE session*'
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user