diff --git a/src/PSProxmoxVE.Core/Client/PveHandlerCache.cs b/src/PSProxmoxVE.Core/Client/PveHandlerCache.cs
new file mode 100644
index 0000000..8d1cfa6
--- /dev/null
+++ b/src/PSProxmoxVE.Core/Client/PveHandlerCache.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+
+namespace PSProxmoxVE.Core.Client
+{
+ ///
+ /// Process-wide cache of instances, one per
+ /// (host, port, skipCertificateCheck). The handler owns the connection pool, so sharing
+ /// it lets every for the same endpoint reuse established TLS
+ /// connections instead of handshaking per request. Cached handlers are never disposed
+ /// and are immutable once published: never set a property on a handler this returns.
+ ///
+ internal sealed class PveHandlerCache
+ {
+ /// The cache production clients share.
+ internal static readonly PveHandlerCache Shared = new PveHandlerCache(CreateHandler);
+
+ private readonly object _gate = new object();
+ private readonly Dictionary _handlers =
+ new Dictionary(StringComparer.OrdinalIgnoreCase);
+ private readonly Func _factory;
+
+ ///
+ /// Test seam: a cache whose handlers come from , which
+ /// receives the skipCertificateCheck flag for the key being populated.
+ ///
+ internal PveHandlerCache(Func factory)
+ {
+ _factory = factory ?? throw new ArgumentNullException(nameof(factory));
+ }
+
+ /// Number of handlers built so far.
+ internal int Count
+ {
+ get { lock (_gate) return _handlers.Count; }
+ }
+
+ /// Returns the handler for the endpoint, building it on first use.
+ internal HttpClientHandler Get(string host, int port, bool skipCertificateCheck)
+ {
+ if (string.IsNullOrWhiteSpace(host))
+ throw new ArgumentException("Host cannot be null or empty.", nameof(host));
+
+ var key = host + ":" + port + ":" + (skipCertificateCheck ? "insecure" : "verify");
+ lock (_gate)
+ {
+ if (!_handlers.TryGetValue(key, out var handler))
+ {
+ handler = _factory(skipCertificateCheck);
+ _handlers[key] = handler;
+ }
+ return handler;
+ }
+ }
+
+ private static HttpClientHandler CreateHandler(bool skipCertificateCheck)
+ {
+ var handler = new HttpClientHandler { UseCookies = false };
+ if (skipCertificateCheck)
+ {
+ handler.ServerCertificateCustomValidationCallback =
+ (HttpRequestMessage _, X509Certificate2 _, X509Chain _, SslPolicyErrors _) => true;
+ }
+ return handler;
+ }
+ }
+}
diff --git a/src/PSProxmoxVE.Core/Client/PveHttpClient.cs b/src/PSProxmoxVE.Core/Client/PveHttpClient.cs
index 2374b50..ffc530f 100644
--- a/src/PSProxmoxVE.Core/Client/PveHttpClient.cs
+++ b/src/PSProxmoxVE.Core/Client/PveHttpClient.cs
@@ -12,9 +12,6 @@ using PSProxmoxVE.Core.Authentication;
using PSProxmoxVE.Core.Exceptions;
using PSProxmoxVE.Core.Utilities;
-using System.Net.Security;
-using System.Security.Cryptography.X509Certificates;
-
namespace PSProxmoxVE.Core.Client
{
///
@@ -28,6 +25,7 @@ namespace PSProxmoxVE.Core.Client
#pragma warning restore CS8625
private readonly string _baseUrl;
private readonly HttpClient _httpClient;
+ private readonly HttpMessageHandler _handler;
private bool _disposed;
private readonly TimeSpan _guestLockRetryWindow;
@@ -47,13 +45,26 @@ namespace PSProxmoxVE.Core.Client
/// to disable the timeout entirely (useful for multi-GB uploads/downloads).
///
public PveHttpClient(PveSession session, TimeSpan? timeoutOverride = null)
- : this(session, timeoutOverride, guestLockRetryWindow: null, handler: null, guestLockRetryDelay: null)
+ : this(session, timeoutOverride, guestLockRetryWindow: null, handler: null, guestLockRetryDelay: null, handlerCache: null)
+ {
+ }
+
+ ///
+ /// Test seam: same as the public constructor but drawing the transport handler from
+ /// instead of .
+ ///
+ /// The authenticated PVE session providing credentials and base URL.
+ /// The cache to take the handler from.
+ internal PveHttpClient(PveSession session, PveHandlerCache handlerCache)
+ : this(session, timeoutOverride: null, guestLockRetryWindow: null, handler: null, guestLockRetryDelay: null,
+ handlerCache: handlerCache ?? throw new ArgumentNullException(nameof(handlerCache)))
{
}
///
/// Test seam: builds a client against an explicit handler, lock-retry window and/or
/// inter-attempt delay. Production code always goes through the public constructor.
+ /// An explicit handler is owned by this client and bypasses the shared handler cache.
///
/// The authenticated PVE session providing credentials and base URL.
/// Optional per-instance timeout override.
@@ -62,42 +73,42 @@ namespace PSProxmoxVE.Core.Client
/// Null uses , the same as the public constructor.
///
///
- /// Message handler to send requests through. Null builds the production
- /// certificate-validation handler from .
+ /// Message handler to send requests through, owned and disposed by this client. Null
+ /// takes the shared pooled handler for the session's endpoint from the handler cache.
///
///
/// Invoked before each guest-lock reissue instead of sleeping. Null uses
/// , the same as the public constructor.
///
+ ///
+ /// Cache to take the handler from when is null. Null uses
+ /// , the same as the public constructor.
+ ///
internal PveHttpClient(
PveSession session,
TimeSpan? timeoutOverride,
TimeSpan? guestLockRetryWindow,
HttpMessageHandler? handler,
- Func? guestLockRetryDelay = null)
+ Func? guestLockRetryDelay = null,
+ PveHandlerCache? handlerCache = null)
{
_session = session ?? throw new ArgumentNullException(nameof(session));
_baseUrl = session.BaseUrl;
_guestLockRetryWindow = guestLockRetryWindow ?? GuestLockRetry.DefaultWindow;
_guestLockRetryDelay = guestLockRetryDelay ?? Task.Delay;
- _httpClient = new HttpClient(handler ?? CreateHandler(session.SkipCertificateCheck));
+ var ownsHandler = handler != null;
+ _handler = handler ?? (handlerCache ?? PveHandlerCache.Shared)
+ .Get(session.Hostname, session.Port, session.SkipCertificateCheck);
+ _httpClient = new HttpClient(_handler, disposeHandler: ownsHandler);
_httpClient.Timeout = timeoutOverride ?? session.Timeout;
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
- private static HttpClientHandler CreateHandler(bool skipCertificateCheck)
- {
- var handler = new HttpClientHandler();
- if (skipCertificateCheck)
- {
- handler.ServerCertificateCustomValidationCallback =
- (HttpRequestMessage _, X509Certificate2 _, X509Chain _, SslPolicyErrors _) => true;
- }
- return handler;
- }
+ /// The transport handler requests go through.
+ internal HttpMessageHandler Handler => _handler;
///
/// Creates a bare HTTP client for pre-session use (e.g. initial authentication).
@@ -113,7 +124,8 @@ namespace PSProxmoxVE.Core.Client
_guestLockRetryWindow = GuestLockRetry.DefaultWindow;
_guestLockRetryDelay = Task.Delay;
- _httpClient = new HttpClient(CreateHandler(skipCertificateCheck));
+ _handler = PveHandlerCache.Shared.Get(hostname, port, skipCertificateCheck);
+ _httpClient = new HttpClient(_handler, disposeHandler: false);
if (timeout.HasValue)
_httpClient.Timeout = timeout.Value;
@@ -506,7 +518,10 @@ namespace PSProxmoxVE.Core.Client
return sb.ToString();
}
- ///
+ ///
+ /// Releases this client. A handler taken from the shared cache stays alive for the
+ /// other clients on the same endpoint; only an explicitly supplied handler is disposed.
+ ///
public void Dispose()
{
if (!_disposed)
diff --git a/src/PSProxmoxVE.Core/Services/BackupService.cs b/src/PSProxmoxVE.Core/Services/BackupService.cs
index c7033a5..4c5ba9b 100644
--- a/src/PSProxmoxVE.Core/Services/BackupService.cs
+++ b/src/PSProxmoxVE.Core/Services/BackupService.cs
@@ -12,10 +12,8 @@ namespace PSProxmoxVE.Core.Services
///
/// Service for Proxmox VE Backup (vzdump) and backup job API operations.
///
- public class BackupService
+ public class BackupService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
///
/// Initializes a new instance of with no injected client.
/// Each method will create and dispose its own .
@@ -27,10 +25,7 @@ namespace PSProxmoxVE.Core.Services
/// The caller owns the client's lifetime; this service will not dispose it.
///
/// The HTTP client to use for all requests.
- public BackupService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public BackupService(IPveHttpClient client) : base(client) { }
// -------------------------------------------------------------------------
// Ad-hoc backup (vzdump)
@@ -48,17 +43,12 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/vzdump", config)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -72,17 +62,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/backup").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -93,18 +78,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"cluster/backup/{Uri.EscapeDataString(id)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -115,15 +95,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("cluster/backup", config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -135,16 +110,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"cluster/backup/{Uri.EscapeDataString(id)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -155,16 +125,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/backup/{Uri.EscapeDataString(id)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -178,18 +143,13 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/backup-info/not-backed-up")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return JsonHelper.ToListOfDictionaries(data as JArray);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
diff --git a/src/PSProxmoxVE.Core/Services/CloudInitService.cs b/src/PSProxmoxVE.Core/Services/CloudInitService.cs
index 443b5df..9252b7a 100644
--- a/src/PSProxmoxVE.Core/Services/CloudInitService.cs
+++ b/src/PSProxmoxVE.Core/Services/CloudInitService.cs
@@ -12,10 +12,8 @@ namespace PSProxmoxVE.Core.Services
/// Service for managing Cloud-Init configuration on Proxmox VE QEMU/KVM VMs.
/// All operations target the /nodes/{node}/qemu/{vmid}/config endpoint.
///
- public class CloudInitService
+ public class CloudInitService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
// Cloud-Init field names as used in the PVE API
private static readonly string[] CloudInitFields =
{
@@ -33,10 +31,7 @@ namespace PSProxmoxVE.Core.Services
/// Initializes a new instance of the class with an injected HTTP client.
///
/// The HTTP client to use for API calls. The caller owns its lifetime.
- public CloudInitService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public CloudInitService(IPveHttpClient client) : base(client) { }
///
/// Retrieves the Cloud-Init specific configuration fields for a VM.
@@ -47,8 +42,7 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/config")
.GetAwaiter().GetResult();
@@ -64,11 +58,7 @@ namespace PSProxmoxVE.Core.Services
}
return ciObj.ToObject() ?? new PveCloudInitConfig();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -89,19 +79,14 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var formData = config.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty);
client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/config", formData)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -114,18 +99,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/cloudinit", null)
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToString() ?? string.Empty;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
}
}
diff --git a/src/PSProxmoxVE.Core/Services/ClusterConfigService.cs b/src/PSProxmoxVE.Core/Services/ClusterConfigService.cs
index 095b9f2..560a9ae 100644
--- a/src/PSProxmoxVE.Core/Services/ClusterConfigService.cs
+++ b/src/PSProxmoxVE.Core/Services/ClusterConfigService.cs
@@ -14,10 +14,8 @@ namespace PSProxmoxVE.Core.Services
/// Service for Proxmox VE cluster configuration API operations
/// (/cluster/config, /cluster/options, /cluster/nextid).
///
- public class ClusterConfigService
+ public class ClusterConfigService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
private static readonly TimeSpan DefaultQuorumTimeout = TimeSpan.FromSeconds(60);
private static readonly TimeSpan QuorumPollInterval = TimeSpan.FromSeconds(2);
@@ -30,10 +28,7 @@ namespace PSProxmoxVE.Core.Services
/// Initializes a new instance of the class with an injected HTTP client.
///
/// The HTTP client to use for API calls. The caller owns its lifetime.
- public ClusterConfigService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public ClusterConfigService(IPveHttpClient client) : base(client) { }
///
/// Returns the cluster configuration directory (GET /cluster/config).
@@ -43,17 +38,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/config").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data is JObject obj ? JsonHelper.ToDictionary(obj) : new Dictionary();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -84,17 +74,12 @@ namespace PSProxmoxVE.Core.Services
if (votes.HasValue)
data["votes"] = votes.Value.ToString();
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync("cluster/config", data).GetAwaiter().GetResult();
var result = JObject.Parse(response)["data"];
return result?.ToString() ?? string.Empty;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -104,17 +89,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/config/nodes").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -151,17 +131,12 @@ namespace PSProxmoxVE.Core.Services
if (apiversion.HasValue)
data["apiversion"] = apiversion.Value.ToString();
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"cluster/config/nodes/{Uri.EscapeDataString(node)}", data).GetAwaiter().GetResult();
var result = JObject.Parse(response)["data"];
return result?.ToString() ?? string.Empty;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -174,15 +149,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrEmpty(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/config/nodes/{Uri.EscapeDataString(node)}").GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -198,17 +168,12 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(node))
resource += $"?node={Uri.EscapeDataString(node!)}";
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync(resource).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveClusterJoinInfo();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -248,17 +213,12 @@ namespace PSProxmoxVE.Core.Services
if (force.HasValue)
data["force"] = force.Value ? "1" : "0";
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync("cluster/config/join", data).GetAwaiter().GetResult();
var result = JObject.Parse(response)["data"];
return result?.ToString() ?? string.Empty;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -268,17 +228,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/config/totem").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return JsonHelper.ToDictionary(data as JObject);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -288,17 +243,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/config/qdevice").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return JsonHelper.ToDictionary(data as JObject);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -308,17 +258,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/config/apiversion").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? 0;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -328,17 +273,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/options").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveClusterOptions();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -351,15 +291,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (options == null) throw new ArgumentNullException(nameof(options));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync("cluster/options", options).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -370,17 +305,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/status").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -439,8 +369,7 @@ namespace PSProxmoxVE.Core.Services
if (vmid.HasValue)
resource += $"?vmid={vmid.Value}";
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync(resource).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
@@ -449,11 +378,7 @@ namespace PSProxmoxVE.Core.Services
if (int.TryParse(data.ToString(), out var id))
return id;
throw new InvalidOperationException($"API returned unexpected next VMID value: '{data}'");
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
}
}
diff --git a/src/PSProxmoxVE.Core/Services/ClusterService.cs b/src/PSProxmoxVE.Core/Services/ClusterService.cs
index 0dcd632..fce46b5 100644
--- a/src/PSProxmoxVE.Core/Services/ClusterService.cs
+++ b/src/PSProxmoxVE.Core/Services/ClusterService.cs
@@ -9,10 +9,8 @@ namespace PSProxmoxVE.Core.Services
///
/// Service for Proxmox VE cluster-level API operations.
///
- public class ClusterService
+ public class ClusterService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
///
/// Initializes a new instance of the class.
///
@@ -22,10 +20,7 @@ namespace PSProxmoxVE.Core.Services
/// Initializes a new instance of the class with an injected HTTP client.
///
/// The HTTP client to use for API calls. The caller owns its lifetime.
- public ClusterService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public ClusterService(IPveHttpClient client) : base(client) { }
///
/// Returns the current cluster status. The response is a mixed array of
@@ -35,17 +30,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/status").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -57,8 +47,7 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var resource = "cluster/resources";
if (!string.IsNullOrEmpty(type))
@@ -67,11 +56,7 @@ namespace PSProxmoxVE.Core.Services
var response = client.GetAsync(resource).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
}
}
diff --git a/src/PSProxmoxVE.Core/Services/ContainerService.cs b/src/PSProxmoxVE.Core/Services/ContainerService.cs
index 67a5be3..dd43769 100644
--- a/src/PSProxmoxVE.Core/Services/ContainerService.cs
+++ b/src/PSProxmoxVE.Core/Services/ContainerService.cs
@@ -12,19 +12,21 @@ namespace PSProxmoxVE.Core.Services
///
/// Service for Proxmox VE Linux Container (LXC) API operations.
///
- public class ContainerService
+ public class ContainerService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
- private readonly NodeService _nodeService = new NodeService();
+ private readonly NodeService _nodeService;
/// Initializes a new instance that creates its own HTTP clients.
- public ContainerService() { }
+ public ContainerService()
+ {
+ _nodeService = new NodeService();
+ }
/// Initializes a new instance that uses the supplied HTTP client for all requests.
/// The HTTP client to use. The caller owns its lifetime.
- public ContainerService(IPveHttpClient client)
+ public ContainerService(IPveHttpClient client) : base(client)
{
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
+ _nodeService = new NodeService(client);
}
// -------------------------------------------------------------------------
@@ -85,17 +87,12 @@ namespace PSProxmoxVE.Core.Services
private PveContainer[] GetContainersOnNode(PveSession session, string node)
{
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/lxc").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -122,18 +119,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/config")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveContainerConfig();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -153,19 +145,14 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var formData = config.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty);
client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/config", formData)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -180,18 +167,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/snapshot")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -215,17 +197,12 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(description))
formData["description"] = description!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/snapshot", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -241,17 +218,12 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.DeleteAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}")
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -267,17 +239,12 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}/rollback")
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -296,8 +263,7 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var formData = config.ToDictionary(
kvp => kvp.Key,
@@ -305,11 +271,7 @@ namespace PSProxmoxVE.Core.Services
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/lxc", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Starts a container. Returns the task UPID.
@@ -334,17 +296,12 @@ namespace PSProxmoxVE.Core.Services
if (timeoutSeconds.HasValue)
formData["timeout"] = timeoutSeconds.Value.ToString();
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/status/shutdown", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Removes a container. Returns the task UPID.
@@ -364,17 +321,12 @@ namespace PSProxmoxVE.Core.Services
queryParams.Add("force=1");
var queryString = "?" + string.Join("&", queryParams);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.DeleteAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}{queryString}")
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Clones a container. Returns the task UPID.
@@ -400,17 +352,12 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(targetNode)) formData["target"] = targetNode!;
if (!string.IsNullOrEmpty(storage)) formData["storage"] = storage!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/clone", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Migrates a container to another node. Returns the task UPID.
@@ -431,17 +378,12 @@ namespace PSProxmoxVE.Core.Services
["online"] = online ? "1" : "0"
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/migrate", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -476,17 +418,12 @@ namespace PSProxmoxVE.Core.Services
["size"] = size
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/resize", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -506,17 +443,12 @@ namespace PSProxmoxVE.Core.Services
["delete"] = delete ? "1" : "0"
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/move_volume", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -531,17 +463,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/template")
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -556,18 +483,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/interfaces")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -579,17 +501,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid}/status/{action}")
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
private static PveTask ParseTask(string response, string node)
diff --git a/src/PSProxmoxVE.Core/Services/FirewallService.cs b/src/PSProxmoxVE.Core/Services/FirewallService.cs
index e458d04..f22593f 100644
--- a/src/PSProxmoxVE.Core/Services/FirewallService.cs
+++ b/src/PSProxmoxVE.Core/Services/FirewallService.cs
@@ -11,19 +11,14 @@ namespace PSProxmoxVE.Core.Services
/// Service for Proxmox VE Firewall API operations.
/// Supports firewall management at cluster, node, VM, and container levels.
///
- public class FirewallService
+ public class FirewallService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
/// Initializes a new instance that creates its own HTTP clients.
public FirewallService() { }
/// Initializes a new instance that uses the supplied HTTP client for all requests.
/// The HTTP client to use. The caller owns its lifetime.
- public FirewallService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public FirewallService(IPveHttpClient client) : base(client) { }
// -------------------------------------------------------------------------
// Rules
@@ -37,17 +32,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"{basePath}/rules").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -60,15 +50,10 @@ namespace PSProxmoxVE.Core.Services
if (config == null) throw new ArgumentNullException(nameof(config));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync($"{basePath}/rules", config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -81,15 +66,10 @@ namespace PSProxmoxVE.Core.Services
if (config == null) throw new ArgumentNullException(nameof(config));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"{basePath}/rules/{pos}", config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -101,15 +81,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"{basePath}/rules/{pos}").GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -123,17 +98,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/firewall/groups").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -148,15 +118,10 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("cluster/firewall/groups", formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -167,16 +132,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/firewall/groups/{Uri.EscapeDataString(name)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -187,18 +147,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -210,16 +165,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -231,16 +181,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}/{pos}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -251,16 +196,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}/{pos}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -275,17 +215,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"{basePath}/aliases").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -307,15 +242,10 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync($"{basePath}/aliases", formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -334,16 +264,11 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"{basePath}/aliases/{Uri.EscapeDataString(name)}", formData)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -356,16 +281,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"{basePath}/aliases/{Uri.EscapeDataString(name)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -380,17 +300,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"{basePath}/ipset").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -407,15 +322,10 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync($"{basePath}/ipset", formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -428,16 +338,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"{basePath}/ipset/{Uri.EscapeDataString(name)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -454,18 +359,13 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"{basePath}/ipset/{Uri.EscapeDataString(name)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -485,16 +385,11 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync($"{basePath}/ipset/{Uri.EscapeDataString(name)}", formData)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -514,17 +409,12 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync(
$"{basePath}/ipset/{Uri.EscapeDataString(name)}/{Uri.EscapeDataString(cidr)}",
formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -538,17 +428,12 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(cidr)) throw new ArgumentNullException(nameof(cidr));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync(
$"{basePath}/ipset/{Uri.EscapeDataString(name)}/{Uri.EscapeDataString(cidr)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -564,17 +449,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"{basePath}/options").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveFirewallOptions();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -587,15 +467,10 @@ namespace PSProxmoxVE.Core.Services
if (config == null) throw new ArgumentNullException(nameof(config));
var basePath = BuildBasePath(level, node, vmid);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"{basePath}/options", config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -615,17 +490,12 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(type))
resource += $"?type={Uri.EscapeDataString(type!)}";
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync(resource).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
diff --git a/src/PSProxmoxVE.Core/Services/HaService.cs b/src/PSProxmoxVE.Core/Services/HaService.cs
index e97bc41..83606d0 100644
--- a/src/PSProxmoxVE.Core/Services/HaService.cs
+++ b/src/PSProxmoxVE.Core/Services/HaService.cs
@@ -11,10 +11,8 @@ namespace PSProxmoxVE.Core.Services
///
/// Service for Proxmox VE High Availability (HA) API operations.
///
- public class HaService
+ public class HaService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
///
/// Initializes a new instance of the class.
///
@@ -24,10 +22,7 @@ namespace PSProxmoxVE.Core.Services
/// Initializes a new instance of the class with an injected HTTP client.
///
/// The HTTP client to use for API calls. The caller owns its lifetime.
- public HaService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public HaService(IPveHttpClient client) : base(client) { }
// -------------------------------------------------------------------------
// Resources
@@ -40,17 +35,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/ha/resources").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -63,18 +53,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(sid)) throw new ArgumentNullException(nameof(sid));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"cluster/ha/resources/{Uri.EscapeDataString(sid)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveHaResource();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -91,15 +76,10 @@ namespace PSProxmoxVE.Core.Services
var formData = new Dictionary(options) { ["sid"] = sid };
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("cluster/ha/resources", formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -114,16 +94,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(sid)) throw new ArgumentNullException(nameof(sid));
if (options == null) throw new ArgumentNullException(nameof(options));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"cluster/ha/resources/{Uri.EscapeDataString(sid)}", options)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -136,16 +111,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(sid)) throw new ArgumentNullException(nameof(sid));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/ha/resources/{Uri.EscapeDataString(sid)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -162,19 +132,14 @@ namespace PSProxmoxVE.Core.Services
var formData = new Dictionary { ["node"] = node };
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync(
$"cluster/ha/resources/{Uri.EscapeDataString(sid)}/migrate", formData)
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToString() ?? string.Empty;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -191,19 +156,14 @@ namespace PSProxmoxVE.Core.Services
var formData = new Dictionary { ["node"] = node };
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync(
$"cluster/ha/resources/{Uri.EscapeDataString(sid)}/relocate", formData)
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToString() ?? string.Empty;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -217,17 +177,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/ha/groups").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -240,18 +195,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"cluster/ha/groups/{Uri.EscapeDataString(group)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveHaGroup();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -274,15 +224,10 @@ namespace PSProxmoxVE.Core.Services
["nodes"] = nodes
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("cluster/ha/groups", formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -297,16 +242,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
if (options == null) throw new ArgumentNullException(nameof(options));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"cluster/ha/groups/{Uri.EscapeDataString(group)}", options)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -319,16 +259,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/ha/groups/{Uri.EscapeDataString(group)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -342,17 +277,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/ha/status/current").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -362,17 +292,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/ha/status/manager_status").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return JsonHelper.ToDictionary(data as JObject);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -386,17 +311,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/ha/rules").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -409,18 +329,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(rule)) throw new ArgumentNullException(nameof(rule));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"cluster/ha/rules/{Uri.EscapeDataString(rule)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveHaRule();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -433,15 +348,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (options == null) throw new ArgumentNullException(nameof(options));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("cluster/ha/rules", options).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -456,16 +366,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(rule)) throw new ArgumentNullException(nameof(rule));
if (options == null) throw new ArgumentNullException(nameof(options));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"cluster/ha/rules/{Uri.EscapeDataString(rule)}", options)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -478,16 +383,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(rule)) throw new ArgumentNullException(nameof(rule));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/ha/rules/{Uri.EscapeDataString(rule)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
}
}
diff --git a/src/PSProxmoxVE.Core/Services/NetworkService.cs b/src/PSProxmoxVE.Core/Services/NetworkService.cs
index 21e22e4..5aded9e 100644
--- a/src/PSProxmoxVE.Core/Services/NetworkService.cs
+++ b/src/PSProxmoxVE.Core/Services/NetworkService.cs
@@ -13,19 +13,14 @@ namespace PSProxmoxVE.Core.Services
/// Service for Proxmox VE node network and SDN (Software-Defined Networking) API operations.
/// SDN methods require PVE 8.0 or later. Version checks are performed at the cmdlet layer.
///
- public class NetworkService
+ public class NetworkService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
/// Initializes a new instance that creates its own HTTP clients.
public NetworkService() { }
/// Initializes a new instance that uses the supplied HTTP client for all requests.
/// The HTTP client to use. The caller owns its lifetime.
- public NetworkService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public NetworkService(IPveHttpClient client) : base(client) { }
// -------------------------------------------------------------------------
// Node network interfaces
@@ -48,17 +43,12 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(type))
resource += $"?type={Uri.EscapeDataString(type!)}";
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync(resource).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -77,8 +67,7 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var formData = config.ToDictionary(
kvp => kvp.Key,
@@ -87,11 +76,7 @@ namespace PSProxmoxVE.Core.Services
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveNetwork();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -113,19 +98,14 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(iface)) throw new ArgumentNullException(nameof(iface));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var formData = config.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty);
client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/network/{Uri.EscapeDataString(iface)}", formData)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -141,15 +121,10 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(iface)) throw new ArgumentNullException(nameof(iface));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"nodes/{Uri.EscapeDataString(node)}/network/{Uri.EscapeDataString(iface)}").GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -162,17 +137,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/network")
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -187,17 +157,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/sdn/zones").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -208,17 +173,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/sdn/vnets").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -233,8 +193,7 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var formData = config.ToDictionary(
kvp => kvp.Key,
@@ -243,11 +202,7 @@ namespace PSProxmoxVE.Core.Services
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveSdnZone();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -260,15 +215,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(zone)) throw new ArgumentNullException(nameof(zone));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/sdn/zones/{Uri.EscapeDataString(zone)}").GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -283,8 +233,7 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var formData = config.ToDictionary(
kvp => kvp.Key,
@@ -293,11 +242,7 @@ namespace PSProxmoxVE.Core.Services
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveSdnVnet();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -310,15 +255,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}").GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -335,18 +275,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -364,19 +299,14 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var formData = config.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty);
client.PostAsync($"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets", formData)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -391,17 +321,12 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
if (string.IsNullOrWhiteSpace(subnet)) throw new ArgumentNullException(nameof(subnet));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync(
$"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets/{Uri.EscapeDataString(subnet)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -415,17 +340,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/sdn/ipams").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -436,15 +356,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("cluster/sdn/ipams", config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -455,16 +370,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(ipam)) throw new ArgumentNullException(nameof(ipam));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/sdn/ipams/{Uri.EscapeDataString(ipam)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -478,17 +388,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/sdn/dns").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -499,15 +404,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("cluster/sdn/dns", config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -518,16 +418,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(dns)) throw new ArgumentNullException(nameof(dns));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/sdn/dns/{Uri.EscapeDataString(dns)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -541,17 +436,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("cluster/sdn/controllers").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -562,15 +452,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("cluster/sdn/controllers", config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -581,16 +466,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(controller)) throw new ArgumentNullException(nameof(controller));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"cluster/sdn/controllers/{Uri.EscapeDataString(controller)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -604,16 +484,11 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync("cluster/sdn", new Dictionary())
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -625,16 +500,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(zone)) throw new ArgumentNullException(nameof(zone));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"cluster/sdn/zones/{Uri.EscapeDataString(zone)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -646,16 +516,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -668,17 +533,12 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(subnet)) throw new ArgumentNullException(nameof(subnet));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync(
$"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets/{Uri.EscapeDataString(subnet)}",
config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -690,16 +550,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(controller)) throw new ArgumentNullException(nameof(controller));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"cluster/sdn/controllers/{Uri.EscapeDataString(controller)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -711,16 +566,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(ipam)) throw new ArgumentNullException(nameof(ipam));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"cluster/sdn/ipams/{Uri.EscapeDataString(ipam)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -732,16 +582,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(dns)) throw new ArgumentNullException(nameof(dns));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"cluster/sdn/dns/{Uri.EscapeDataString(dns)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
diff --git a/src/PSProxmoxVE.Core/Services/NodeService.cs b/src/PSProxmoxVE.Core/Services/NodeService.cs
index e9c4436..a8c64e7 100644
--- a/src/PSProxmoxVE.Core/Services/NodeService.cs
+++ b/src/PSProxmoxVE.Core/Services/NodeService.cs
@@ -13,10 +13,8 @@ namespace PSProxmoxVE.Core.Services
///
/// Service for node-level and cluster-version Proxmox VE API operations.
///
- public class NodeService
+ public class NodeService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
///
/// Initializes a new instance of with no injected client.
/// Each method will create and dispose its own .
@@ -28,10 +26,7 @@ namespace PSProxmoxVE.Core.Services
/// The caller owns the client's lifetime; this service will not dispose it.
///
/// The HTTP client to use for all requests.
- public NodeService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public NodeService(IPveHttpClient client) : base(client) { }
///
/// Returns all cluster nodes.
@@ -40,17 +35,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("nodes").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -61,17 +51,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/status").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveNodeStatus();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -84,17 +69,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/config").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return JsonHelper.ToDictionary(data as JObject);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -109,15 +89,10 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/config", config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -130,17 +105,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/dns").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return JsonHelper.ToDictionary(data as JObject);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -155,15 +125,10 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/dns", config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -178,16 +143,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
var formData = config ?? new Dictionary();
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/startall", formData).GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -202,16 +162,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
var formData = config ?? new Dictionary();
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/stopall", formData).GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -221,8 +176,7 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("version").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
@@ -230,11 +184,7 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrEmpty(versionStr))
throw new InvalidOperationException("Failed to retrieve PVE version from API response.");
return PveVersion.Parse(versionStr!);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
diff --git a/src/PSProxmoxVE.Core/Services/PoolService.cs b/src/PSProxmoxVE.Core/Services/PoolService.cs
index e76d964..c8bbb23 100644
--- a/src/PSProxmoxVE.Core/Services/PoolService.cs
+++ b/src/PSProxmoxVE.Core/Services/PoolService.cs
@@ -10,10 +10,8 @@ namespace PSProxmoxVE.Core.Services
///
/// Service for Proxmox VE resource pool API operations.
///
- public class PoolService
+ public class PoolService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
///
/// Initializes a new instance of with no injected client.
/// Each method will create and dispose its own .
@@ -25,10 +23,7 @@ namespace PSProxmoxVE.Core.Services
/// The caller owns the client's lifetime; this service will not dispose it.
///
/// The HTTP client to use for all requests.
- public PoolService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public PoolService(IPveHttpClient client) : base(client) { }
///
/// Returns all resource pools.
@@ -37,17 +32,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("pools").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -58,18 +48,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"pools/{Uri.EscapeDataString(poolId)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -80,19 +65,14 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var config = new Dictionary { { "poolid", poolId } };
if (!string.IsNullOrEmpty(comment))
config["comment"] = comment!;
client.PostAsync("pools", config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -104,16 +84,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"pools/{Uri.EscapeDataString(poolId)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -124,16 +99,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"pools/{Uri.EscapeDataString(poolId)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
}
}
diff --git a/src/PSProxmoxVE.Core/Services/PveServiceBase.cs b/src/PSProxmoxVE.Core/Services/PveServiceBase.cs
new file mode 100644
index 0000000..230fae8
--- /dev/null
+++ b/src/PSProxmoxVE.Core/Services/PveServiceBase.cs
@@ -0,0 +1,86 @@
+using System;
+using PSProxmoxVE.Core.Authentication;
+using PSProxmoxVE.Core.Client;
+
+namespace PSProxmoxVE.Core.Services
+{
+ ///
+ /// Common base for the API services: owns the optional injected
+ /// and the client lifetime around each API operation. A service built without a client
+ /// opens one per operation and disposes it afterwards; a service built with one uses it
+ /// for every operation and never disposes it, since the caller owns its lifetime.
+ ///
+ public abstract class PveServiceBase
+ {
+ private readonly IPveHttpClient? _injectedClient;
+
+ /// Initializes a service that opens its own HTTP client per call.
+ private protected PveServiceBase() { }
+
+ /// Initializes a service that uses the supplied HTTP client for every call.
+ /// The HTTP client to use. The caller owns its lifetime.
+ private protected PveServiceBase(IPveHttpClient client)
+ {
+ _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
+ }
+
+ ///
+ /// Runs against the injected client, or against a client
+ /// opened for this call and disposed when it returns.
+ ///
+ /// The result type.
+ /// The authenticated PVE session.
+ /// The work to run against the client.
+ private protected T Invoke(PveSession session, Func action) =>
+ Invoke(session, timeoutOverride: null, action);
+
+ ///
+ /// Runs against the injected client, or against a client
+ /// opened for this call and disposed when it returns.
+ ///
+ /// The authenticated PVE session.
+ /// The work to run against the client.
+ private protected void Invoke(PveSession session, Action action)
+ {
+ if (action == null) throw new ArgumentNullException(nameof(action));
+ Invoke
- public class SnapshotService
+ public class SnapshotService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
///
/// Initializes a new instance of the class.
///
@@ -24,10 +22,7 @@ namespace PSProxmoxVE.Core.Services
/// Initializes a new instance of the class with an injected HTTP client.
///
/// The HTTP client to use for API calls. The caller owns its lifetime.
- public SnapshotService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public SnapshotService(IPveHttpClient client) : base(client) { }
///
/// Returns all snapshots for a VM.
@@ -40,18 +35,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/snapshot")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -83,17 +73,12 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(description))
formData["description"] = description!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/snapshot", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -113,17 +98,12 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.DeleteAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}")
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -143,17 +123,12 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}/rollback")
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
diff --git a/src/PSProxmoxVE.Core/Services/StorageService.cs b/src/PSProxmoxVE.Core/Services/StorageService.cs
index 5b4162e..e537c53 100644
--- a/src/PSProxmoxVE.Core/Services/StorageService.cs
+++ b/src/PSProxmoxVE.Core/Services/StorageService.cs
@@ -12,10 +12,8 @@ namespace PSProxmoxVE.Core.Services
///
/// Service for Proxmox VE storage API operations.
///
- public class StorageService
+ public class StorageService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
///
/// Initializes a new instance of with no injected client.
/// Each method will create and dispose its own .
@@ -27,10 +25,7 @@ namespace PSProxmoxVE.Core.Services
/// The caller owns the client's lifetime; this service will not dispose it.
///
/// The HTTP client to use for all requests.
- public StorageService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public StorageService(IPveHttpClient client) : base(client) { }
// -------------------------------------------------------------------------
// Read operations
@@ -50,17 +45,12 @@ namespace PSProxmoxVE.Core.Services
? $"nodes/{Uri.EscapeDataString(node)}/storage"
: "storage";
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync(resource).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -86,17 +76,12 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(contentType))
resource += $"?content={Uri.EscapeDataString(contentType!)}";
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync(resource).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -137,8 +122,7 @@ namespace PSProxmoxVE.Core.Services
["content"] = "iso"
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session, timeout ?? TimeSpan.FromMinutes(30));
- try
+ return Invoke(session, timeout ?? TimeSpan.FromMinutes(30), client =>
{
var response = client.UploadFileAsync(
$"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/upload",
@@ -149,11 +133,7 @@ namespace PSProxmoxVE.Core.Services
progressCallback)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -187,17 +167,12 @@ namespace PSProxmoxVE.Core.Services
["content"] = contentType
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/download-url", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -215,8 +190,7 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var formData = config.ToDictionary(
kvp => kvp.Key,
@@ -224,11 +198,7 @@ namespace PSProxmoxVE.Core.Services
var response = client.PostAsync("storage", formData).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveStorage();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -241,15 +211,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"storage/{Uri.EscapeDataString(storage)}").GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -264,16 +229,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"storage/{Uri.EscapeDataString(storage)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -292,17 +252,12 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/status").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveStorageStatus();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -319,16 +274,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
if (string.IsNullOrWhiteSpace(volume)) throw new ArgumentNullException(nameof(volume));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/content/{Uri.EscapeDataString(volume)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -347,16 +297,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(volume)) throw new ArgumentNullException(nameof(volume));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/content/{Uri.EscapeDataString(volume)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -373,17 +318,12 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/content", config)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
diff --git a/src/PSProxmoxVE.Core/Services/TaskService.cs b/src/PSProxmoxVE.Core/Services/TaskService.cs
index 512a36b..e9a49d8 100644
--- a/src/PSProxmoxVE.Core/Services/TaskService.cs
+++ b/src/PSProxmoxVE.Core/Services/TaskService.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
+using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using PSProxmoxVE.Core.Authentication;
using PSProxmoxVE.Core.Client;
@@ -12,24 +13,50 @@ namespace PSProxmoxVE.Core.Services
///
/// Service for querying and waiting on Proxmox VE asynchronous tasks (UPIDs).
///
- public class TaskService
+ public class TaskService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(10);
- private static readonly TimeSpan DefaultPollInterval = TimeSpan.FromSeconds(2);
private static readonly TimeSpan MinPollInterval = TimeSpan.FromSeconds(1);
+ private static readonly TimeSpan MaxBackoffInterval = TimeSpan.FromSeconds(10);
+ private static readonly TimeSpan BackoffStep = TimeSpan.FromSeconds(1);
+
+ private readonly Func _pollDelay;
/// Initializes a new instance that creates its own HTTP clients.
- public TaskService() { }
+ public TaskService() : this(Sleep) { }
/// Initializes a new instance that uses the supplied HTTP client for all requests.
/// The HTTP client to use. The caller owns its lifetime.
- public TaskService(IPveHttpClient client)
+ public TaskService(IPveHttpClient client) : this(client, Sleep) { }
+
+ ///
+ /// Test seam: same as but with the wait between polls
+ /// replaceable, so a test can assert the poll schedule without sleeping for it.
+ ///
+ /// Invoked with each computed poll interval instead of sleeping.
+ internal TaskService(Func pollDelay)
{
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
+ _pollDelay = pollDelay ?? throw new ArgumentNullException(nameof(pollDelay));
}
+ ///
+ /// Test seam: same as but with the wait between
+ /// polls replaceable, so a test can assert the poll schedule without sleeping for it.
+ ///
+ /// The HTTP client to use. The caller owns its lifetime.
+ /// Invoked with each computed poll interval instead of sleeping.
+ internal TaskService(IPveHttpClient client, Func pollDelay) : base(client)
+ {
+ _pollDelay = pollDelay ?? throw new ArgumentNullException(nameof(pollDelay));
+ }
+
+ private static Task Sleep(TimeSpan duration)
+ {
+ Thread.Sleep(duration);
+ return Task.CompletedTask;
+ }
+
+
///
/// Returns the current status of a task identified by its UPID.
///
@@ -39,21 +66,21 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(upid)) throw new ArgumentNullException(nameof(upid));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var encodedUpid = Uri.EscapeDataString(upid);
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/tasks/{encodedUpid}/status")
.GetAwaiter().GetResult();
- var data = JObject.Parse(response)["data"];
- var task = data?.ToObject() ?? new PveTask { Upid = upid };
- task.Node = node;
- return task;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ return ParseTaskStatus(response, node, upid);
+ });
+ }
+
+ private static PveTask ParseTaskStatus(string response, string node, string upid)
+ {
+ var data = JObject.Parse(response)["data"];
+ var task = data?.ToObject() ?? new PveTask { Upid = upid };
+ task.Node = node;
+ return task;
}
///
@@ -65,29 +92,29 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(upid)) throw new ArgumentNullException(nameof(upid));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var encodedUpid = Uri.EscapeDataString(upid);
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/tasks/{encodedUpid}/log")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
- /// Polls the task status until it completes, throws on timeout or failure.
+ /// Polls the task status until it completes, throws on timeout or failure. One HTTP
+ /// client is held open for the whole wait.
///
/// Active PVE session.
/// Node name where the task is running.
/// Task UPID.
/// Maximum time to wait. Defaults to 10 minutes.
- /// Interval between status polls. Defaults to 2 seconds. Minimum 1 second.
+ ///
+ /// Fixed interval between status polls, minimum 1 second. When omitted the interval
+ /// starts at 1 second and grows by 1 second per poll up to a 10 second cap. A wait
+ /// never sleeps past .
+ ///
/// Optional callback invoked on each poll with the current task.
/// The completed .
/// Thrown when the task does not complete within .
@@ -105,29 +132,44 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(upid)) throw new ArgumentNullException(nameof(upid));
var effectiveTimeout = timeout ?? DefaultTimeout;
- var effectivePoll = pollInterval ?? DefaultPollInterval;
- if (effectivePoll < MinPollInterval)
- effectivePoll = MinPollInterval;
+ var fixedInterval = pollInterval.HasValue
+ ? (pollInterval.Value < MinPollInterval ? MinPollInterval : pollInterval.Value)
+ : (TimeSpan?)null;
var deadline = DateTime.UtcNow.Add(effectiveTimeout);
+ var statusResource = $"nodes/{Uri.EscapeDataString(node)}/tasks/{Uri.EscapeDataString(upid)}/status";
- while (true)
+ return Invoke(session, client =>
{
- var task = GetTask(session, node, upid);
- progressCallback?.Invoke(task);
-
- if (string.Equals(task.Status, "stopped", StringComparison.OrdinalIgnoreCase))
+ var interval = fixedInterval ?? MinPollInterval;
+ while (true)
{
- if (!string.Equals(task.ExitStatus, "OK", StringComparison.OrdinalIgnoreCase))
- throw new PveTaskFailedException(upid, task.ExitStatus ?? "(no exit status)");
- return task;
+ var response = client.GetAsync(statusResource).GetAwaiter().GetResult();
+ var task = ParseTaskStatus(response, node, upid);
+ progressCallback?.Invoke(task);
+
+ if (string.Equals(task.Status, "stopped", StringComparison.OrdinalIgnoreCase))
+ {
+ if (!string.Equals(task.ExitStatus, "OK", StringComparison.OrdinalIgnoreCase))
+ throw new PveTaskFailedException(upid, task.ExitStatus ?? "(no exit status)");
+ return task;
+ }
+
+ var now = DateTime.UtcNow;
+ if (now >= deadline)
+ throw new PveTaskTimeoutException(upid, effectiveTimeout);
+
+ var remaining = deadline - now;
+ _pollDelay(interval < remaining ? interval : remaining).GetAwaiter().GetResult();
+
+ if (!fixedInterval.HasValue && interval < MaxBackoffInterval)
+ {
+ interval += BackoffStep;
+ if (interval > MaxBackoffInterval)
+ interval = MaxBackoffInterval;
+ }
}
-
- if (DateTime.UtcNow >= deadline)
- throw new PveTaskTimeoutException(upid, effectiveTimeout);
-
- Thread.Sleep(effectivePoll);
- }
+ });
}
///
@@ -145,8 +187,7 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var queryParts = new List { $"limit={limit}" };
if (vmid.HasValue)
@@ -164,11 +205,7 @@ namespace PSProxmoxVE.Core.Services
foreach (var t in tasks)
t.Node ??= node;
return tasks;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -183,17 +220,12 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(upid)) throw new ArgumentNullException(nameof(upid));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var encodedUpid = Uri.EscapeDataString(upid);
client.DeleteAsync($"nodes/{Uri.EscapeDataString(node)}/tasks/{encodedUpid}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
}
}
diff --git a/src/PSProxmoxVE.Core/Services/TemplateService.cs b/src/PSProxmoxVE.Core/Services/TemplateService.cs
index 3fa53a2..6f05627 100644
--- a/src/PSProxmoxVE.Core/Services/TemplateService.cs
+++ b/src/PSProxmoxVE.Core/Services/TemplateService.cs
@@ -11,23 +11,25 @@ namespace PSProxmoxVE.Core.Services
/// Service for Proxmox VE VM template operations.
/// Templates are VMs with the "template" flag set to 1.
///
- public class TemplateService
+ public class TemplateService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
- private readonly VmService _vmService = new VmService();
+ private readonly VmService _vmService;
///
/// Initializes a new instance of the class.
///
- public TemplateService() { }
+ public TemplateService()
+ {
+ _vmService = new VmService();
+ }
///
/// Initializes a new instance of the class with an injected HTTP client.
///
/// The HTTP client to use for API calls. The caller owns its lifetime.
- public TemplateService(IPveHttpClient client)
+ public TemplateService(IPveHttpClient client) : base(client)
{
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
+ _vmService = new VmService(client);
}
///
@@ -58,17 +60,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/template")
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
diff --git a/src/PSProxmoxVE.Core/Services/UserService.cs b/src/PSProxmoxVE.Core/Services/UserService.cs
index 5c07f32..e0295ba 100644
--- a/src/PSProxmoxVE.Core/Services/UserService.cs
+++ b/src/PSProxmoxVE.Core/Services/UserService.cs
@@ -11,10 +11,8 @@ namespace PSProxmoxVE.Core.Services
///
/// Service for Proxmox VE access control — users, roles, and permissions (ACLs).
///
- public class UserService
+ public class UserService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
-
///
/// Initializes a new instance of with no injected client.
/// Each method will create and dispose its own .
@@ -26,10 +24,7 @@ namespace PSProxmoxVE.Core.Services
/// The caller owns the client's lifetime; this service will not dispose it.
///
/// The HTTP client to use for all requests.
- public UserService(IPveHttpClient client)
- {
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
- }
+ public UserService(IPveHttpClient client) : base(client) { }
// -------------------------------------------------------------------------
// Users
@@ -41,17 +36,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("access/users").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Returns a single user by their user ID (e.g. "admin@pam").
@@ -62,8 +52,7 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var encodedId = Uri.EscapeDataString(userId);
var response = client.GetAsync($"access/users/{encodedId}").GetAwaiter().GetResult();
@@ -73,11 +62,7 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrEmpty(user.UserId))
user.UserId = userId;
return user;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -101,15 +86,10 @@ namespace PSProxmoxVE.Core.Services
formData[kvp.Key] = kvp.Value?.ToString() ?? string.Empty;
}
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("access/users", formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Removes a user account.
@@ -120,16 +100,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var encodedId = Uri.EscapeDataString(userId);
client.DeleteAsync($"access/users/{encodedId}").GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Updates one or more properties of an existing user.
@@ -145,19 +120,14 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var encodedId = Uri.EscapeDataString(userId);
var formData = config.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty);
client.PutAsync($"access/users/{encodedId}", formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -172,8 +142,7 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var encodedId = Uri.EscapeDataString(userId);
var response = client.GetAsync($"access/users/{encodedId}/token").GetAwaiter().GetResult();
@@ -182,11 +151,7 @@ namespace PSProxmoxVE.Core.Services
foreach (var t in tokens)
t.UserId = userId;
return tokens;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -218,8 +183,7 @@ namespace PSProxmoxVE.Core.Services
if (expire.HasValue) formData["expire"] = expire.Value.ToString();
if (privilegeSeparation.HasValue) formData["privsep"] = privilegeSeparation.Value ? "1" : "0";
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var encodedUser = Uri.EscapeDataString(userId);
var encodedToken = Uri.EscapeDataString(tokenId);
@@ -232,11 +196,7 @@ namespace PSProxmoxVE.Core.Services
token.UserId = userId;
token.TokenId = tokenId;
return token;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Removes an API token.
@@ -249,18 +209,13 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId));
if (string.IsNullOrWhiteSpace(tokenId)) throw new ArgumentNullException(nameof(tokenId));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var encodedUser = Uri.EscapeDataString(userId);
var encodedToken = Uri.EscapeDataString(tokenId);
client.DeleteAsync($"access/users/{encodedUser}/token/{encodedToken}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -273,18 +228,13 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(tokenId)) throw new ArgumentNullException(nameof(tokenId));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var encodedUser = Uri.EscapeDataString(userId);
var encodedToken = Uri.EscapeDataString(tokenId);
client.PutAsync($"access/users/{encodedUser}/token/{encodedToken}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -297,17 +247,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("access/roles").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Creates a new role.
@@ -323,15 +268,10 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(privileges))
formData["privs"] = privileges!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("access/roles", formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Removes a role.
@@ -342,16 +282,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(roleId)) throw new ArgumentNullException(nameof(roleId));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"access/roles/{Uri.EscapeDataString(roleId)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -367,16 +302,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(privileges)) throw new ArgumentNullException(nameof(privileges));
var formData = new Dictionary { ["privs"] = privileges };
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"access/roles/{Uri.EscapeDataString(roleId)}", formData)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -389,17 +319,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("access/groups").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Creates a new group.
@@ -415,15 +340,10 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("access/groups", formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Updates a group's properties.
@@ -436,16 +356,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(groupId)) throw new ArgumentNullException(nameof(groupId));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"access/groups/{Uri.EscapeDataString(groupId)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Removes a group.
@@ -456,16 +371,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(groupId)) throw new ArgumentNullException(nameof(groupId));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"access/groups/{Uri.EscapeDataString(groupId)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -478,17 +388,12 @@ namespace PSProxmoxVE.Core.Services
{
if (session == null) throw new ArgumentNullException(nameof(session));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync("access/domains").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Creates a new authentication domain/realm.
@@ -499,15 +404,10 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync("access/domains", config).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Updates an authentication domain/realm.
@@ -520,16 +420,11 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(realm)) throw new ArgumentNullException(nameof(realm));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"access/domains/{Uri.EscapeDataString(realm)}", config)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Removes an authentication domain/realm.
@@ -540,16 +435,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(realm)) throw new ArgumentNullException(nameof(realm));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.DeleteAsync($"access/domains/{Uri.EscapeDataString(realm)}")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -572,15 +462,10 @@ namespace PSProxmoxVE.Core.Services
["password"] = password
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync("access/password", formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -609,8 +494,7 @@ namespace PSProxmoxVE.Core.Services
if (queryParts.Count > 0)
resource += "?" + string.Join("&", queryParts);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync(resource).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
@@ -635,11 +519,7 @@ namespace PSProxmoxVE.Core.Services
result.Add(perm);
}
return result.ToArray();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -678,15 +558,10 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(groups)) formData["groups"] = groups!;
if (!string.IsNullOrEmpty(tokens)) formData["tokens"] = tokens!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync("access/acl", formData).GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
}
}
diff --git a/src/PSProxmoxVE.Core/Services/VmService.cs b/src/PSProxmoxVE.Core/Services/VmService.cs
index 347acfc..1de2033 100644
--- a/src/PSProxmoxVE.Core/Services/VmService.cs
+++ b/src/PSProxmoxVE.Core/Services/VmService.cs
@@ -13,19 +13,21 @@ namespace PSProxmoxVE.Core.Services
///
/// Service for Proxmox VE QEMU/KVM virtual machine API operations.
///
- public class VmService
+ public class VmService : PveServiceBase
{
- private readonly IPveHttpClient? _injectedClient;
- private readonly NodeService _nodeService = new NodeService();
+ private readonly NodeService _nodeService;
/// Initializes a new instance that creates its own HTTP clients.
- public VmService() { }
+ public VmService()
+ {
+ _nodeService = new NodeService();
+ }
/// Initializes a new instance that uses the supplied HTTP client for all requests.
/// The HTTP client to use. The caller owns its lifetime.
- public VmService(IPveHttpClient client)
+ public VmService(IPveHttpClient client) : base(client)
{
- _injectedClient = client ?? throw new ArgumentNullException(nameof(client));
+ _nodeService = new NodeService(client);
}
// -------------------------------------------------------------------------
@@ -90,17 +92,12 @@ namespace PSProxmoxVE.Core.Services
private PveVm[] GetVmsOnNode(PveSession session, string node)
{
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -135,8 +132,7 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (vm == null) throw new ArgumentNullException(nameof(vm));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vm.VmId}/status/current")
.GetAwaiter().GetResult();
@@ -150,11 +146,7 @@ namespace PSProxmoxVE.Core.Services
vm.CpuCount = data["cpus"]?.ToObject() ?? vm.CpuCount;
vm.MaxMem = data["maxmem"]?.ToObject() ?? vm.MaxMem;
vm.MaxDisk = data["maxdisk"]?.ToObject() ?? vm.MaxDisk;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -168,18 +160,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/config")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveVmConfig();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -199,19 +186,14 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
var formData = config.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty);
client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/config", formData)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -262,18 +244,13 @@ namespace PSProxmoxVE.Core.Services
[disk] = diskValue
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
// POST (not PUT) because import-from triggers a background task
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/config", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -292,8 +269,7 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (config == null) throw new ArgumentNullException(nameof(config));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var formData = config.ToDictionary(
kvp => kvp.Key,
@@ -301,11 +277,7 @@ namespace PSProxmoxVE.Core.Services
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Starts a VM. Returns the task UPID.
@@ -336,17 +308,12 @@ namespace PSProxmoxVE.Core.Services
if (timeoutSeconds.HasValue)
formData["timeout"] = timeoutSeconds.Value.ToString();
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/status/shutdown", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -372,17 +339,12 @@ namespace PSProxmoxVE.Core.Services
if (timeoutSeconds.HasValue)
formData["timeout"] = timeoutSeconds.Value.ToString();
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/status/reboot", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
/// Resets a VM (hard reset). Returns the task UPID.
@@ -429,17 +391,12 @@ namespace PSProxmoxVE.Core.Services
queryParams.Add("skiplock=1");
var queryString = "?" + string.Join("&", queryParams);
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.DeleteAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}{queryString}")
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -474,17 +431,12 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(targetNode)) formData["target"] = targetNode!;
if (!string.IsNullOrEmpty(storage)) formData["storage"] = storage!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/clone", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -512,17 +464,12 @@ namespace PSProxmoxVE.Core.Services
["online"] = online ? "1" : "0"
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/migrate", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -553,17 +500,12 @@ namespace PSProxmoxVE.Core.Services
["size"] = size
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/resize", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -575,17 +517,12 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/status/{action}")
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
private static PveTask ParseTask(string response, string node)
@@ -612,21 +549,19 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
- client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/agent/ping").GetAwaiter().GetResult();
- return true;
- }
- catch (PSProxmoxVE.Core.Exceptions.PveApiException)
- {
- // Treat API-level failures for this endpoint as "guest agent not responding".
- return false;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ try
+ {
+ client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/agent/ping").GetAwaiter().GetResult();
+ return true;
+ }
+ catch (PSProxmoxVE.Core.Exceptions.PveApiException)
+ {
+ // Treat API-level failures for this endpoint as "guest agent not responding".
+ return false;
+ }
+ });
}
///
@@ -637,19 +572,14 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/agent/network-get-interfaces")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
var result = data?["result"];
return result?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -663,8 +593,7 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(command)) throw new ArgumentNullException(nameof(command));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
// PVE's agent/exec "command" is an array: element 0 is the executable and
// each subsequent element is one argv entry. It is sent as repeated form
@@ -688,11 +617,7 @@ namespace PSProxmoxVE.Core.Services
.GetAwaiter().GetResult();
var pid = JObject.Parse(response)["data"]?["pid"]?.ToObject() ?? 0;
return pid;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -703,18 +628,13 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/agent/exec-status?pid={pid}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return JsonHelper.ToDictionary(data as JObject);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -740,17 +660,12 @@ namespace PSProxmoxVE.Core.Services
if (!string.IsNullOrEmpty(format))
formData["format"] = format!;
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/move_disk", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -769,16 +684,11 @@ namespace PSProxmoxVE.Core.Services
if (force)
formData["force"] = "1";
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/unlink", formData)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -793,19 +703,14 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/agent/get-osinfo")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
var result = data?["result"];
return result?.ToObject();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -816,19 +721,14 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/agent/get-fsinfo")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
var result = data?["result"];
return result?.ToObject() ?? Array.Empty();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -840,18 +740,13 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(file)) throw new ArgumentNullException(nameof(file));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/agent/file-read?file={Uri.EscapeDataString(file)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?["content"]?.ToString() ?? string.Empty;
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -869,16 +764,11 @@ namespace PSProxmoxVE.Core.Services
["content"] = content
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/agent/file-write", formData)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -898,16 +788,11 @@ namespace PSProxmoxVE.Core.Services
if (crypted)
formData["crypted"] = "1";
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/agent/set-user-password", formData)
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
///
@@ -918,16 +803,11 @@ namespace PSProxmoxVE.Core.Services
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
- try
+ Invoke(session, client =>
{
client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/agent/fstrim")
.GetAwaiter().GetResult();
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
// -------------------------------------------------------------------------
@@ -967,8 +847,7 @@ namespace PSProxmoxVE.Core.Services
["content"] = "import"
};
- IPveHttpClient client = _injectedClient ?? new PveHttpClient(session, timeout ?? TimeSpan.FromMinutes(30));
- try
+ return Invoke(session, timeout ?? TimeSpan.FromMinutes(30), client =>
{
var response = client.UploadFileAsync(
$"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/upload",
@@ -980,11 +859,7 @@ namespace PSProxmoxVE.Core.Services
var root = JObject.Parse(response);
var upid = root["data"]?.ToString() ?? string.Empty;
return new PveTask { Upid = upid, Node = node, Status = "running" };
- }
- finally
- {
- if (_injectedClient == null) client.Dispose();
- }
+ });
}
}
}
diff --git a/src/PSProxmoxVE/Cmdlets/Tasks/WaitPveTaskCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Tasks/WaitPveTaskCmdlet.cs
index 1fc70e1..37c4570 100644
--- a/src/PSProxmoxVE/Cmdlets/Tasks/WaitPveTaskCmdlet.cs
+++ b/src/PSProxmoxVE/Cmdlets/Tasks/WaitPveTaskCmdlet.cs
@@ -45,7 +45,8 @@ namespace PSProxmoxVE.Cmdlets.Tasks
public TimeSpan? Timeout { get; set; }
///
- /// How frequently to poll the task status. Defaults to 2 seconds.
+ /// Fixed interval between status polls, minimum 1 second. When omitted, polling
+ /// starts at 1 second and backs off to a 10 second cap.
/// Example: -PollInterval (New-TimeSpan -Seconds 5)
///
[Parameter(Mandatory = false, HelpMessage = "How often to poll task status.")]
diff --git a/tests/PSProxmoxVE.Core.Tests/Client/PveHandlerCacheTests.cs b/tests/PSProxmoxVE.Core.Tests/Client/PveHandlerCacheTests.cs
new file mode 100644
index 0000000..ce71486
--- /dev/null
+++ b/tests/PSProxmoxVE.Core.Tests/Client/PveHandlerCacheTests.cs
@@ -0,0 +1,144 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using PSProxmoxVE.Core.Authentication;
+using PSProxmoxVE.Core.Client;
+using Xunit;
+
+namespace PSProxmoxVE.Core.Tests.Client
+{
+ public class PveHandlerCacheTests
+ {
+ private static PveSession NewSession(bool skipCertificateCheck = true, string host = "pve.example.com") =>
+ new PveSession(host, 8006, skipCertificateCheck,
+ "root@pam!token=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
+
+ private sealed class CannedHandler : HttpClientHandler
+ {
+ public int Sends;
+ public bool IsDisposed;
+
+ protected override void Dispose(bool disposing)
+ {
+ IsDisposed = true;
+ base.Dispose(disposing);
+ }
+
+ protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ if (IsDisposed) throw new ObjectDisposedException(nameof(CannedHandler));
+ Interlocked.Increment(ref Sends);
+ return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent("{\"data\":{\"version\":\"8.2\"}}")
+ });
+ }
+ }
+
+ private static (PveHandlerCache cache, List built) NewCache()
+ {
+ var built = new List();
+ var cache = new PveHandlerCache(_ =>
+ {
+ var h = new CannedHandler();
+ built.Add(h);
+ return h;
+ });
+ return (cache, built);
+ }
+
+ [Fact]
+ public void TwoClientsForTheSameEndpointShareOneHandler()
+ {
+ var (cache, built) = NewCache();
+
+ using var first = new PveHttpClient(NewSession(), cache);
+ using var second = new PveHttpClient(NewSession(), cache);
+
+ Assert.Single(built);
+ Assert.Same(first.Handler, second.Handler);
+ Assert.Equal(1, cache.Count);
+ }
+
+ [Fact]
+ public void DifferentSkipCertificateCheckGetsADifferentHandler()
+ {
+ var (cache, built) = NewCache();
+
+ using var insecure = new PveHttpClient(NewSession(skipCertificateCheck: true), cache);
+ using var verified = new PveHttpClient(NewSession(skipCertificateCheck: false), cache);
+
+ Assert.Equal(2, built.Count);
+ Assert.NotSame(insecure.Handler, verified.Handler);
+ }
+
+ [Fact]
+ public void DifferentHostGetsADifferentHandler()
+ {
+ var (cache, _) = NewCache();
+
+ using var a = new PveHttpClient(NewSession(host: "pve-a.example.com"), cache);
+ using var b = new PveHttpClient(NewSession(host: "pve-b.example.com"), cache);
+
+ Assert.NotSame(a.Handler, b.Handler);
+ }
+
+ [Fact]
+ public async Task DisposingOneClientLeavesTheSharedHandlerUsableByAnother()
+ {
+ var (cache, built) = NewCache();
+
+ var first = new PveHttpClient(NewSession(), cache);
+ using var second = new PveHttpClient(NewSession(), cache);
+ first.Dispose();
+
+ var body = await second.GetAsync("version");
+
+ Assert.Contains("8.2", body);
+ Assert.Equal(1, built[0].Sends);
+ Assert.Single(built);
+ Assert.False(built[0].IsDisposed);
+ }
+
+ [Fact]
+ public void AnExplicitHandlerBypassesTheCacheAndIsOwnedByTheClient()
+ {
+ var (cache, built) = NewCache();
+ var own = new CannedHandler();
+
+ var client = new PveHttpClient(NewSession(), timeoutOverride: null,
+ guestLockRetryWindow: null, handler: own, guestLockRetryDelay: null, handlerCache: cache);
+
+ Assert.Same(own, client.Handler);
+ Assert.Empty(built);
+
+ client.Dispose();
+
+ Assert.True(own.IsDisposed);
+ }
+
+ [Fact]
+ public void SharedHandlersDoNotCarryACookieContainer()
+ {
+ var handler = PveHandlerCache.Shared.Get("cookies.example.invalid", 8006, skipCertificateCheck: false);
+
+ Assert.False(handler.UseCookies);
+ }
+
+ [Fact]
+ public void GetBuildsEachKeyOnceUnderConcurrentCallers()
+ {
+ var (cache, built) = NewCache();
+ var handlers = new HttpClientHandler[32];
+
+ Parallel.For(0, handlers.Length, i =>
+ handlers[i] = cache.Get("pve.example.com", 8006, skipCertificateCheck: true));
+
+ Assert.Single(built);
+ Assert.All(handlers, h => Assert.Same(built[0], h));
+ }
+ }
+}
diff --git a/tests/PSProxmoxVE.Core.Tests/Services/ContainerServiceTests.cs b/tests/PSProxmoxVE.Core.Tests/Services/ContainerServiceTests.cs
index 533a547..bb445fa 100644
--- a/tests/PSProxmoxVE.Core.Tests/Services/ContainerServiceTests.cs
+++ b/tests/PSProxmoxVE.Core.Tests/Services/ContainerServiceTests.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
-using System.Reflection;
using Moq;
using PSProxmoxVE.Core.Authentication;
using PSProxmoxVE.Core.Client;
@@ -123,20 +122,6 @@ namespace PSProxmoxVE.Core.Tests.Services
// GetContainers multi-node aggregation: issue #142
// ---------------------------------------------------------------------
- ///
- /// Points the private NodeService field a ContainerService constructs at a mock
- /// client, so the "list all nodes" call the multi-node overload issues is reachable
- /// without a real HTTP connection. ContainerService(client) only injects the client
- /// used for the per-node lxc calls; NodeService is never constructor-injectable from
- /// ContainerService, so this is the only offline path to the aggregation loop.
- ///
- private static void InjectNodeServiceClient(ContainerService service, IPveHttpClient client)
- {
- var field = typeof(ContainerService).GetField("_nodeService", BindingFlags.NonPublic | BindingFlags.Instance)
- ?? throw new InvalidOperationException("ContainerService._nodeService field not found.");
- field.SetValue(service, new NodeService(client));
- }
-
private static Mock SetupTwoNodeCluster()
{
var mockClient = new Mock();
@@ -158,7 +143,6 @@ namespace PSProxmoxVE.Core.Tests.Services
.ThrowsAsync(new PveApiException(HttpStatusCode.InternalServerError, "internal error", "nodes/pve2/lxc", "GET"));
var service = new ContainerService(mockClient.Object);
- InjectNodeServiceClient(service, mockClient.Object);
var skipped = new List();
var containers = service.GetContainers(CreateSession(), onNodeSkipped: (node, ex) => skipped.Add(node));
@@ -180,7 +164,6 @@ namespace PSProxmoxVE.Core.Tests.Services
.ThrowsAsync(new PveApiException(HttpStatusCode.Forbidden, "permission denied", "nodes/pve2/lxc", "GET"));
var service = new ContainerService(mockClient.Object);
- InjectNodeServiceClient(service, mockClient.Object);
var ex = Assert.Throws(() => service.GetContainers(CreateSession()));
Assert.Equal(HttpStatusCode.Forbidden, ex.StatusCode);
@@ -203,7 +186,6 @@ namespace PSProxmoxVE.Core.Tests.Services
"nodes/pve2/lxc", "GET", new HttpRequestException("connection refused")));
var service = new ContainerService(mockClient.Object);
- InjectNodeServiceClient(service, mockClient.Object);
var skipped = new List();
var containers = service.GetContainers(CreateSession(), onNodeSkipped: (node, ex) => skipped.Add(node));
@@ -230,7 +212,6 @@ namespace PSProxmoxVE.Core.Tests.Services
"nodes/pve2/lxc", "GET"));
var service = new ContainerService(mockClient.Object);
- InjectNodeServiceClient(service, mockClient.Object);
var skipped = new List();
var containers = service.GetContainers(CreateSession(), onNodeSkipped: (node, ex) => skipped.Add(node));
diff --git a/tests/PSProxmoxVE.Core.Tests/Services/PveServiceBaseTests.cs b/tests/PSProxmoxVE.Core.Tests/Services/PveServiceBaseTests.cs
new file mode 100644
index 0000000..5983c9f
--- /dev/null
+++ b/tests/PSProxmoxVE.Core.Tests/Services/PveServiceBaseTests.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using PSProxmoxVE.Core.Authentication;
+using PSProxmoxVE.Core.Client;
+using PSProxmoxVE.Core.Services;
+using Xunit;
+
+namespace PSProxmoxVE.Core.Tests.Services
+{
+ public class PveServiceBaseTests
+ {
+ private static PveSession CreateSession() =>
+ new PveSession("pve1.example.com", 8006, true, "PVE:root@pam:TEST_TOKEN");
+
+ private sealed class RecordingClient : IPveHttpClient
+ {
+ public int DisposeCalls;
+ public int Gets;
+
+ public Task GetAsync(string resource) { Gets++; return Task.FromResult("{\"data\":null}"); }
+ public Task PostAsync(string resource, Dictionary