refactor: wire Group level onto the firewall rule cmdlets (#126) (#202)

FirewallService.GetGroupRules/CreateGroupRule/UpdateGroupRule/RemoveGroupRule
already existed with zero callers. Get/New/Set/Remove-PveFirewallRule gain a
Group value in their -Level ValidateSet and a -Group parameter, required and
validated the same way Node/VmId are validated for the other levels, and
dispatch to those service methods instead of the generic BuildBasePath-driven
ones. No cmdlet in this area built its own client, so unlike the other #126
areas there is no inline request to strip.

Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
This commit is contained in:
goodolclint-claude[bot]
2026-09-03 00:14:47 +00:00
committed by GitHub
parent 87fa7a7a9c
commit 283dc47658
6 changed files with 485 additions and 19 deletions
@@ -10,8 +10,8 @@ namespace PSProxmoxVE.Cmdlets.Firewall
[OutputType(typeof(PveFirewallRule))]
public sealed class GetPveFirewallRuleCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, Container, or Group.")]
[ValidateSet("Cluster", "Node", "Vm", "Container", "Group")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
@@ -21,13 +21,17 @@ namespace PSProxmoxVE.Cmdlets.Firewall
[ValidateRange(100, 999999999)]
public int? VmId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The security group name. Required when Level is Group.")]
public string? Group { 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.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
@@ -48,13 +52,25 @@ namespace PSProxmoxVE.Cmdlets.Firewall
return;
}
}
if (string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrWhiteSpace(Group))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Group is required when Level is Group."),
"GroupRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
var session = GetSession();
var service = new FirewallService();
var vmid = VmId;
WriteVerbose($"Getting firewall rules at level '{level}'...");
var rules = service.GetRules(session, level, Node, vmid);
var rules = string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase)
? service.GetGroupRules(session, Group!)
: service.GetRules(session, level, Node, vmid);
if (Position.HasValue)
{
@@ -10,8 +10,8 @@ namespace PSProxmoxVE.Cmdlets.Firewall
[OutputType(typeof(PveFirewallRule))]
public sealed class NewPveFirewallRuleCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, Container, or Group.")]
[ValidateSet("Cluster", "Node", "Vm", "Container", "Group")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
@@ -21,6 +21,9 @@ namespace PSProxmoxVE.Cmdlets.Firewall
[ValidateRange(100, 999999999)]
public int? VmId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The security group name. Required when Level is Group.")]
public string? Group { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The rule type: in, out, or group.")]
[ValidateSet("in", "out", "group")]
public string Type { get; set; } = string.Empty;
@@ -62,7 +65,8 @@ namespace PSProxmoxVE.Cmdlets.Firewall
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
@@ -83,8 +87,21 @@ namespace PSProxmoxVE.Cmdlets.Firewall
return;
}
}
if (string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrWhiteSpace(Group))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Group is required when Level is Group."),
"GroupRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall rule ({Level})", "Create"))
var target = string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase)
? $"firewall rule ({Level} '{Group}')"
: $"firewall rule ({Level})";
if (!ShouldProcess(target, "Create"))
return;
var session = GetSession();
@@ -119,7 +136,10 @@ namespace PSProxmoxVE.Cmdlets.Firewall
config["iface"] = Iface!;
WriteVerbose($"Creating firewall rule at level '{level}'...");
service.CreateRule(session, level, config, Node, vmid);
if (string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase))
service.CreateGroupRule(session, Group!, config);
else
service.CreateRule(session, level, config, Node, vmid);
}
}
}
@@ -9,8 +9,8 @@ namespace PSProxmoxVE.Cmdlets.Firewall
[OutputType(typeof(void))]
public sealed class RemovePveFirewallRuleCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, Container, or Group.")]
[ValidateSet("Cluster", "Node", "Vm", "Container", "Group")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
@@ -20,13 +20,17 @@ namespace PSProxmoxVE.Cmdlets.Firewall
[ValidateRange(100, 999999999)]
public int? VmId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The security group name. Required when Level is Group.")]
public string? Group { 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.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
@@ -47,8 +51,21 @@ namespace PSProxmoxVE.Cmdlets.Firewall
return;
}
}
if (string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrWhiteSpace(Group))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Group is required when Level is Group."),
"GroupRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall rule at position {Position} ({Level})", "Remove"))
var target = string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase)
? $"firewall rule at position {Position} ({Level} '{Group}')"
: $"firewall rule at position {Position} ({Level})";
if (!ShouldProcess(target, "Remove"))
return;
var session = GetSession();
@@ -56,7 +73,10 @@ namespace PSProxmoxVE.Cmdlets.Firewall
var vmid = VmId;
WriteVerbose($"Removing firewall rule at position {Position} ({level})...");
service.RemoveRule(session, level, Position, Node, vmid);
if (string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase))
service.RemoveGroupRule(session, Group!, Position);
else
service.RemoveRule(session, level, Position, Node, vmid);
}
}
}
@@ -10,8 +10,8 @@ namespace PSProxmoxVE.Cmdlets.Firewall
[OutputType(typeof(void))]
public sealed class SetPveFirewallRuleCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, Container, or Group.")]
[ValidateSet("Cluster", "Node", "Vm", "Container", "Group")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
@@ -21,6 +21,9 @@ namespace PSProxmoxVE.Cmdlets.Firewall
[ValidateRange(100, 999999999)]
public int? VmId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The security group name. Required when Level is Group.")]
public string? Group { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The rule position to update.")]
public int Position { get; set; }
@@ -65,7 +68,8 @@ namespace PSProxmoxVE.Cmdlets.Firewall
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
@@ -86,8 +90,21 @@ namespace PSProxmoxVE.Cmdlets.Firewall
return;
}
}
if (string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrWhiteSpace(Group))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Group is required when Level is Group."),
"GroupRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall rule at position {Position} ({Level})", "Update"))
var target = string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase)
? $"firewall rule at position {Position} ({Level} '{Group}')"
: $"firewall rule at position {Position} ({Level})";
if (!ShouldProcess(target, "Update"))
return;
var session = GetSession();
@@ -122,7 +139,10 @@ namespace PSProxmoxVE.Cmdlets.Firewall
config["iface"] = Iface!;
WriteVerbose($"Updating firewall rule at position {Position} ({level})...");
service.UpdateRule(session, level, Position, config, Node, vmid);
if (string.Equals(level, "Group", StringComparison.OrdinalIgnoreCase))
service.UpdateGroupRule(session, Group!, Position, config);
else
service.UpdateRule(session, level, Position, config, Node, vmid);
}
}
}