mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-08-09 05:49:24 +00:00
Merge main into fix/http-client-timeout
Resolves findings.json conflict — F086 (from #60, merged into main) and F087 (this branch) both append to the trailing findings array. Kept both entries, in numeric order. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PSProxmoxVE.Core.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses storage size strings (e.g. "32G", "1T", "60") and normalizes them
|
||||
/// to a bare integer count of gibibytes for use in PVE disk specs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// PVE accepts a size suffix on file-backed storages (NFS, directory) but parses
|
||||
/// the value after the colon as a volume name on LVM-backed storages — so
|
||||
/// <c>local-lvm:32G</c> fails with "unable to parse lvm volume name '32G'" while
|
||||
/// <c>local-lvm:32</c> works on every storage type. Cmdlets that build disk specs
|
||||
/// must normalize size inputs through this helper before joining with the storage.
|
||||
/// </remarks>
|
||||
public static class SizeParser
|
||||
{
|
||||
private static readonly Regex Pattern = new Regex(
|
||||
@"^\s*(?<num>\d+)\s*(?<unit>[A-Za-z]*)\s*$",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Parses a size string and returns the value as a bare integer count of GiB.
|
||||
/// Accepts values like "60", "60G", "60GB" (= 60), "1T", "1TB" (= 1024).
|
||||
/// Sub-GB units are rejected because PVE disk allocation is GB-granular.
|
||||
/// </summary>
|
||||
/// <param name="value">The size string supplied by the user.</param>
|
||||
/// <param name="parameterName">Parameter name used in the error message.</param>
|
||||
/// <returns>The size in whole GiB as a string, suitable for direct use in disk specs.</returns>
|
||||
/// <exception cref="ArgumentException">The input cannot be parsed or uses an unsupported unit.</exception>
|
||||
public static string NormalizeToGibibytes(string value, string parameterName = "size")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
throw new ArgumentException($"{parameterName} must not be null or empty.", parameterName);
|
||||
|
||||
var match = Pattern.Match(value);
|
||||
if (!match.Success)
|
||||
throw new ArgumentException(
|
||||
$"{parameterName} '{value}' is not a valid size. Expected a positive integer optionally suffixed with G, GB, T, or TB (e.g. '32G', '1T', '60').",
|
||||
parameterName);
|
||||
|
||||
if (!long.TryParse(match.Groups["num"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num) || num <= 0)
|
||||
throw new ArgumentException(
|
||||
$"{parameterName} '{value}' must be a positive integer.",
|
||||
parameterName);
|
||||
|
||||
var unit = match.Groups["unit"].Value.ToUpperInvariant();
|
||||
long gib;
|
||||
switch (unit)
|
||||
{
|
||||
case "":
|
||||
case "G":
|
||||
case "GB":
|
||||
case "GIB":
|
||||
gib = num;
|
||||
break;
|
||||
case "T":
|
||||
case "TB":
|
||||
case "TIB":
|
||||
try { gib = checked(num * 1024L); }
|
||||
catch (OverflowException)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"{parameterName} '{value}' is too large to represent in GiB.",
|
||||
parameterName);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException(
|
||||
$"{parameterName} '{value}' uses unsupported unit '{unit}'. Use G, GB, T, or TB. Sub-GB units (M, MB, K, KB) are not supported by PVE disk allocation.",
|
||||
parameterName);
|
||||
}
|
||||
|
||||
return gib.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Newtonsoft.Json.Linq;
|
||||
using PSProxmoxVE.Core.Client;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
using PSProxmoxVE.Core.Utilities;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Containers
|
||||
{
|
||||
@@ -59,9 +60,13 @@ namespace PSProxmoxVE.Cmdlets.Containers
|
||||
public int? Cores { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Size of the root filesystem (e.g., "8G").</para>
|
||||
/// <para type="description">
|
||||
/// Size of the root filesystem. Accepts a bare integer in GiB ("8") or a value
|
||||
/// suffixed with G/GB/T/TB (case-insensitive); the value is normalized to a
|
||||
/// bare GiB count before being sent to the API.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Size of the root filesystem (e.g. 8G).")]
|
||||
[Parameter(Mandatory = false, HelpMessage = "Size of the root filesystem in GiB (e.g. 8 or 8G).")]
|
||||
public string? RootFsSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -125,6 +130,13 @@ namespace PSProxmoxVE.Cmdlets.Containers
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
// Validate -RootFsSize before ShouldProcess so typos like "512M" are rejected
|
||||
// even with -WhatIf, and so the error is raised regardless of whether
|
||||
// -RootFsStorage is also supplied.
|
||||
string? rootFsSizeGib = null;
|
||||
if (!string.IsNullOrEmpty(RootFsSize))
|
||||
rootFsSizeGib = SizeParser.NormalizeToGibibytes(RootFsSize!, nameof(RootFsSize));
|
||||
|
||||
if (!ShouldProcess($"Container on node '{Node}'", "New-PveContainer"))
|
||||
return;
|
||||
|
||||
@@ -160,8 +172,8 @@ namespace PSProxmoxVE.Cmdlets.Containers
|
||||
if (!string.IsNullOrEmpty(RootFsStorage))
|
||||
{
|
||||
var rootFsValue = RootFsStorage!;
|
||||
if (!string.IsNullOrEmpty(RootFsSize))
|
||||
rootFsValue += $":{RootFsSize}";
|
||||
if (rootFsSizeGib != null)
|
||||
rootFsValue += $":{rootFsSizeGib}";
|
||||
config["rootfs"] = rootFsValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using Newtonsoft.Json.Linq;
|
||||
using PSProxmoxVE.Core.Client;
|
||||
using PSProxmoxVE.Core.Models.Vms;
|
||||
using PSProxmoxVE.Core.Services;
|
||||
using PSProxmoxVE.Core.Utilities;
|
||||
|
||||
namespace PSProxmoxVE.Cmdlets.Vms
|
||||
{
|
||||
@@ -74,9 +75,13 @@ namespace PSProxmoxVE.Cmdlets.Vms
|
||||
public string? Machine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para type="description">Size of the primary disk (e.g., "32G").</para>
|
||||
/// <para type="description">
|
||||
/// Size of the primary disk. Accepts a bare integer in GiB ("32") or a value
|
||||
/// suffixed with G/GB/T/TB (case-insensitive); the value is normalized to a
|
||||
/// bare GiB count before being sent to the API.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = false, HelpMessage = "Size of the primary disk (e.g. 32G).")]
|
||||
[Parameter(Mandatory = false, HelpMessage = "Size of the primary disk in GiB (e.g. 32 or 32G).")]
|
||||
public string? DiskSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -123,6 +128,13 @@ namespace PSProxmoxVE.Cmdlets.Vms
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
// Validate -DiskSize before ShouldProcess so typos like "512M" are rejected
|
||||
// even with -WhatIf, and so the error is raised regardless of whether
|
||||
// -DiskStorage is also supplied.
|
||||
string? diskSizeGib = null;
|
||||
if (!string.IsNullOrEmpty(DiskSize))
|
||||
diskSizeGib = SizeParser.NormalizeToGibibytes(DiskSize!, nameof(DiskSize));
|
||||
|
||||
if (!ShouldProcess($"VM on node '{Node}'", "New-PveVm"))
|
||||
return;
|
||||
|
||||
@@ -161,9 +173,9 @@ namespace PSProxmoxVE.Cmdlets.Vms
|
||||
if (!string.IsNullOrEmpty(OsType))
|
||||
config["ostype"] = OsType!;
|
||||
|
||||
if (!string.IsNullOrEmpty(DiskStorage) && !string.IsNullOrEmpty(DiskSize))
|
||||
if (!string.IsNullOrEmpty(DiskStorage) && diskSizeGib != null)
|
||||
{
|
||||
var diskValue = $"{DiskStorage}:{DiskSize}";
|
||||
var diskValue = $"{DiskStorage}:{diskSizeGib}";
|
||||
if (!string.IsNullOrEmpty(DiskFormat))
|
||||
diskValue += $",format={DiskFormat}";
|
||||
config["virtio0"] = diskValue;
|
||||
|
||||
Reference in New Issue
Block a user