diff --git a/src/PodmanClient/Models/Image/ImagePullStatusResponse.cs b/src/PodmanClient/Models/Image/ImagePullStatusResponse.cs deleted file mode 100644 index 7ebccb8..0000000 --- a/src/PodmanClient/Models/Image/ImagePullStatusResponse.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MaksIT.PodmanClientDotNet.Models.Image { - public class ImagePullStatusResponse { - public string Status { get; set; } - public string Id { get; set; } - public string Progress { get; set; } - public ProgressDetail ProgressDetail { get; set; } - } -} diff --git a/src/PodmanClient/Models/Image/ImagePullStreamResponse.cs b/src/PodmanClient/Models/Image/ImagePullStreamResponse.cs deleted file mode 100644 index 9f4ff21..0000000 --- a/src/PodmanClient/Models/Image/ImagePullStreamResponse.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MaksIT.PodmanClientDotNet.Models.Image { - public class ImagePullStreamResponse { - public string Stream { get; set; } - } -} diff --git a/src/PodmanClient/Models/Image/PullImageResponse.cs b/src/PodmanClient/Models/Image/PullImageResponse.cs new file mode 100644 index 0000000..576ac8e --- /dev/null +++ b/src/PodmanClient/Models/Image/PullImageResponse.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MaksIT.PodmanClientDotNet.Models.Image +{ + public class PullImageResponse + { + + /// + /// Error contains text of errors from c/image. + /// + public string Error { get; set; } + + /// + /// ID contains image ID (retained for backwards compatibility). + /// + public string Id { get; set; } + + /// + /// Images contains the IDs of the images pulled. + /// + public List Images { get; set; } + + /// + /// Stream used to provide output from c/image. + /// + public string Stream { get; set; } + } +} diff --git a/src/PodmanClient/PodmanClient.cs b/src/PodmanClient/PodmanClient.cs new file mode 100644 index 0000000..74c2a9e --- /dev/null +++ b/src/PodmanClient/PodmanClient.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Logging; + +namespace MaksIT.PodmanClientDotNet { + public partial class PodmanClient { + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + private const string _apiVersion = "v1.41"; + + /// + /// Initializes a new instance of the class using the specified base address and timeout. + /// + /// The logger instance used for logging within the client. + /// The base address of the Podman service. + /// The timeout period in minutes for HTTP requests. + public PodmanClient( + ILogger logger, + string baseAddress, + int timeOut = 60 + ) : this( + logger, + baseAddress, + new HttpClient { + Timeout = TimeSpan.FromMinutes(timeOut) + } + ) { } + + /// + /// Initializes a new instance of the class using the provided instance. + /// + /// The logger instance used for logging within the client. + /// An existing instance configured for use with the Podman service. + /// Thrown when the logger or httpClient parameter is null. + public PodmanClient( + ILogger logger, + string serverUrl, + HttpClient httpClient + ) { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + if (serverUrl == null) + throw new ArgumentNullException(nameof(serverUrl)); + + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + + ConfigureHttpClient(serverUrl); + } + + /// + /// Configures the default settings for the used by this instance. + /// Ensures that the "Accept" header is set to "application/json". + /// + private void ConfigureHttpClient(string baseAddress) { + _httpClient.BaseAddress = new Uri(baseAddress); + + if (_httpClient.DefaultRequestHeaders.Contains("Accept")) + _httpClient.DefaultRequestHeaders.Remove("Accept"); + + _httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); + } + } +} diff --git a/src/PodmanClient/PodmanConnectorContainer.cs b/src/PodmanClient/PodmanClientContainer.cs similarity index 78% rename from src/PodmanClient/PodmanConnectorContainer.cs rename to src/PodmanClient/PodmanClientContainer.cs index 80e7890..a02b8cd 100644 --- a/src/PodmanClient/PodmanConnectorContainer.cs +++ b/src/PodmanClient/PodmanClientContainer.cs @@ -1,10 +1,12 @@ using System.Text; using System.Text.Json; -using MaksIT.PodmanClientDotNet.Extensions; +using Microsoft.Extensions.Logging; using MaksIT.PodmanClientDotNet.Models; using MaksIT.PodmanClientDotNet.Models.Container; +using MaksIT.PodmanClientDotNet.Extensions; + namespace MaksIT.PodmanClientDotNet { public partial class PodmanClient { @@ -246,31 +248,31 @@ namespace MaksIT.PodmanClientDotNet { }; var jsonContent = new StringContent(JsonSerializer.Serialize(createContainerParameters), Encoding.UTF8, "application/json"); - var response = await _httpClient.PostAsync("/v1.41/libpod/containers/create", jsonContent); + var response = await _httpClient.PostAsync($"/{_apiVersion}/libpod/containers/create", jsonContent); if (response.IsSuccessStatusCode) { var jsonResponse = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(jsonResponse); + return jsonResponse.ToObject(); } else { var errorContent = await response.Content.ReadAsStringAsync(); - var errorDetails = JsonSerializer.Deserialize(errorContent); + var errorDetails = errorContent.ToObject(); switch (response.StatusCode) { case System.Net.HttpStatusCode.BadRequest: - Console.WriteLine($"Bad parameter in request: {errorDetails?.Message}"); + _logger.LogError($"Bad parameter in request: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.NotFound: - Console.WriteLine($"No such container: {errorDetails?.Message}"); + _logger.LogError($"No such container: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.Conflict: - Console.WriteLine($"Conflict error in operation: {errorDetails?.Message}"); + _logger.LogError($"Conflict error in operation: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.InternalServerError: - Console.WriteLine($"Internal server error: {errorDetails?.Message}"); + _logger.LogError($"Internal server error: {errorDetails?.Message}"); break; default: - Console.WriteLine($"Error creating container: {errorDetails?.Message}"); + _logger.LogError($"Error creating container: {errorDetails?.Message}"); break; } @@ -284,34 +286,34 @@ namespace MaksIT.PodmanClientDotNet { public async Task StartContainerAsync(string containerId, string detachKeys = "ctrl-p,ctrl-q") { var response = await _httpClient.PostAsync( - $"/v1.41/libpod/containers/{containerId}/start?detachKeys={Uri.EscapeDataString(detachKeys)}", null); + $"/{_apiVersion}/libpod/containers/{containerId}/start?detachKeys={Uri.EscapeDataString(detachKeys)}", null); switch (response.StatusCode) { case System.Net.HttpStatusCode.NoContent: - Console.WriteLine("Container started successfully."); + _logger.LogInformation("Container started successfully."); break; case System.Net.HttpStatusCode.NotModified: - Console.WriteLine("Container was already started."); + _logger.LogWarning("Container was already started."); break; case System.Net.HttpStatusCode.NotFound: var errorContent404 = await response.Content.ReadAsStringAsync(); var errorDetails404 = errorContent404.ToObject(); - Console.WriteLine($"Container not found: {errorDetails404?.Message}"); + _logger.LogError($"Container not found: {errorDetails404?.Message}"); break; case System.Net.HttpStatusCode.InternalServerError: var errorContent500 = await response.Content.ReadAsStringAsync(); var errorDetails500 = errorContent500.ToObject(); - Console.WriteLine($"Internal server error: {errorDetails500?.Message}"); + _logger.LogError($"Internal server error: {errorDetails500?.Message}"); break; default: if ((int)response.StatusCode >= 400) { var errorContent = await response.Content.ReadAsStringAsync(); var errorDetails = errorContent.ToObject(); - Console.WriteLine($"Error starting container: {errorDetails?.Message}"); + _logger.LogError($"Error starting container: {errorDetails?.Message}"); } break; } @@ -321,14 +323,14 @@ namespace MaksIT.PodmanClientDotNet { public async Task StopContainerAsync(string containerId, int timeout = 10, bool ignoreAlreadyStopped = false) { var queryParams = $"?timeout={timeout}&Ignore={ignoreAlreadyStopped.ToString().ToLower()}"; - var response = await _httpClient.PostAsync($"/v1.41/libpod/containers/{containerId}/stop{queryParams}", null); + var response = await _httpClient.PostAsync($"/{_apiVersion}/libpod/containers/{containerId}/stop{queryParams}", null); if (response.IsSuccessStatusCode) { if (response.StatusCode == System.Net.HttpStatusCode.NoContent) { - Console.WriteLine("Container stopped successfully."); + _logger.LogInformation("Container stopped successfully."); } else if (response.StatusCode == System.Net.HttpStatusCode.NotModified) { - Console.WriteLine("Container was already stopped."); + _logger.LogWarning("Container was already stopped."); } } else { @@ -337,15 +339,15 @@ namespace MaksIT.PodmanClientDotNet { switch (response.StatusCode) { case System.Net.HttpStatusCode.NotFound: - Console.WriteLine($"No such container: {errorDetails?.Message}"); + _logger.LogError($"No such container: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.InternalServerError: - Console.WriteLine($"Internal server error: {errorDetails?.Message}"); + _logger.LogError($"Internal server error: {errorDetails?.Message}"); break; default: - Console.WriteLine($"Error stopping container: {errorDetails?.Message}"); + _logger.LogError($"Error stopping container: {errorDetails?.Message}"); break; } @@ -355,49 +357,49 @@ namespace MaksIT.PodmanClientDotNet { public async Task ForceDeleteContainerAsync(string containerId, bool deleteVolumes = false, int timeout = 10) { var queryParams = $"?force=true&v={deleteVolumes.ToString().ToLower()}&timeout={timeout}"; - var response = await _httpClient.DeleteAsync($"/v1.41/libpod/containers/{containerId}{queryParams}"); + var response = await _httpClient.DeleteAsync($"/{_apiVersion}/libpod/containers/{containerId}{queryParams}"); if (response.IsSuccessStatusCode) { if (response.StatusCode == System.Net.HttpStatusCode.NoContent) { - Console.WriteLine("Container force deleted successfully."); + _logger.LogInformation("Container force deleted successfully."); } else if (response.StatusCode == System.Net.HttpStatusCode.OK) { var responseContent = await response.Content.ReadAsStringAsync(); - var deleteResponses = JsonSerializer.Deserialize(responseContent); + var deleteResponses = responseContent.ToObject(); foreach (var deleteResponse in deleteResponses) { if (string.IsNullOrEmpty(deleteResponse.Err)) { - Console.WriteLine($"Container {deleteResponse.Id} deleted successfully."); + _logger.LogInformation($"Container {deleteResponse.Id} deleted successfully."); } else { - Console.WriteLine($"Error deleting container {deleteResponse.Id}: {deleteResponse.Err}"); + _logger.LogError($"Error deleting container {deleteResponse.Id}: {deleteResponse.Err}"); } } } } else { var errorContent = await response.Content.ReadAsStringAsync(); - var errorDetails = JsonSerializer.Deserialize(errorContent); + var errorDetails = errorContent.ToObject(); switch (response.StatusCode) { case System.Net.HttpStatusCode.BadRequest: - Console.WriteLine($"Bad parameter in request: {errorDetails?.Message}"); + _logger.LogError($"Bad parameter in request: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.NotFound: - Console.WriteLine($"No such container: {errorDetails?.Message}"); + _logger.LogError($"No such container: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.Conflict: - Console.WriteLine($"Conflict error: {errorDetails?.Message}"); + _logger.LogError($"Conflict error: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.InternalServerError: - Console.WriteLine($"Internal server error: {errorDetails?.Message}"); + _logger.LogError($"Internal server error: {errorDetails?.Message}"); break; default: - Console.WriteLine($"Error deleting container: {errorDetails?.Message}"); + _logger.LogError($"Error deleting container: {errorDetails?.Message}"); break; } @@ -407,11 +409,11 @@ namespace MaksIT.PodmanClientDotNet { public async Task DeleteContainerAsync(string containerId, bool depend = false, bool ignore = false, int timeout = 10) { var queryParams = $"?depend={depend.ToString().ToLower()}&ignore={ignore.ToString().ToLower()}&timeout={timeout}"; - var response = await _httpClient.DeleteAsync($"/v1.41/libpod/containers/{containerId}{queryParams}"); + var response = await _httpClient.DeleteAsync($"/libpod/containers/{containerId}{queryParams}"); if (response.IsSuccessStatusCode) { if (response.StatusCode == System.Net.HttpStatusCode.NoContent) { - Console.WriteLine("Container deleted successfully."); + _logger.LogInformation("Container deleted successfully."); } else if (response.StatusCode == System.Net.HttpStatusCode.OK) { var responseContent = await response.Content.ReadAsStringAsync(); @@ -419,10 +421,10 @@ namespace MaksIT.PodmanClientDotNet { foreach (var deleteResponse in deleteResponses) { if (string.IsNullOrEmpty(deleteResponse.Err)) { - Console.WriteLine($"Container {deleteResponse.Id} deleted successfully."); + _logger.LogInformation($"Container {deleteResponse.Id} deleted successfully."); } else { - Console.WriteLine($"Error deleting container {deleteResponse.Id}: {deleteResponse.Err}"); + _logger.LogInformation($"Error deleting container {deleteResponse.Id}: {deleteResponse.Err}"); } } } @@ -433,23 +435,23 @@ namespace MaksIT.PodmanClientDotNet { switch (response.StatusCode) { case System.Net.HttpStatusCode.BadRequest: - Console.WriteLine($"Bad parameter in request: {errorDetails?.Message}"); + _logger.LogInformation($"Bad parameter in request: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.NotFound: - Console.WriteLine($"No such container: {errorDetails?.Message}"); + _logger.LogInformation($"No such container: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.Conflict: - Console.WriteLine($"Conflict error: {errorDetails?.Message}"); + _logger.LogInformation($"Conflict error: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.InternalServerError: - Console.WriteLine($"Internal server error: {errorDetails?.Message}"); + _logger.LogInformation($"Internal server error: {errorDetails?.Message}"); break; default: - Console.WriteLine($"Error deleting container: {errorDetails?.Message}"); + _logger.LogInformation($"Error deleting container: {errorDetails?.Message}"); break; } @@ -464,34 +466,34 @@ namespace MaksIT.PodmanClientDotNet { content.Headers.Add("Content-Type", "application/x-tar"); var queryParams = $"?path={Uri.EscapeDataString(path)}&pause={pause.ToString().ToLower()}"; - var response = await _httpClient.PutAsync($"/v1.41/libpod/containers/{containerId}/archive{queryParams}", content); + var response = await _httpClient.PutAsync($"/{_apiVersion}/libpod/containers/{containerId}/archive{queryParams}", content); if (response.IsSuccessStatusCode) { - Console.WriteLine("Files copied successfully to the container."); + _logger.LogInformation("Files copied successfully to the container."); } else { var errorContent = await response.Content.ReadAsStringAsync(); - var errorDetails = JsonSerializer.Deserialize(errorContent); + var errorDetails = errorContent.ToObject(); switch (response.StatusCode) { case System.Net.HttpStatusCode.BadRequest: - Console.WriteLine($"Bad parameter in request: {errorDetails?.Message}"); + _logger.LogError($"Bad parameter in request: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.Forbidden: - Console.WriteLine($"The container root filesystem is read-only: {errorDetails?.Message}"); + _logger.LogError($"The container root filesystem is read-only: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.NotFound: - Console.WriteLine($"No such container: {errorDetails?.Message}"); + _logger.LogError($"No such container: {errorDetails?.Message}"); break; case System.Net.HttpStatusCode.InternalServerError: - Console.WriteLine($"Internal server error: {errorDetails?.Message}"); + _logger.LogError($"Internal server error: {errorDetails?.Message}"); break; default: - Console.WriteLine($"Error copying files: {errorDetails?.Message}"); + _logger.LogError($"Error copying files: {errorDetails?.Message}"); break; } diff --git a/src/PodmanClient/PodmanClientDotNet.csproj b/src/PodmanClient/PodmanClientDotNet.csproj index c4cb102..da9dc2b 100644 --- a/src/PodmanClient/PodmanClientDotNet.csproj +++ b/src/PodmanClient/PodmanClientDotNet.csproj @@ -19,4 +19,8 @@ false + + + + diff --git a/src/PodmanClient/PodmanConnectorExec.cs b/src/PodmanClient/PodmanClientExec.cs similarity index 58% rename from src/PodmanClient/PodmanConnectorExec.cs rename to src/PodmanClient/PodmanClientExec.cs index caa82ee..103d563 100644 --- a/src/PodmanClient/PodmanConnectorExec.cs +++ b/src/PodmanClient/PodmanClientExec.cs @@ -1,26 +1,28 @@ using System.Text; -using System.Text.Json; -using MaksIT.PodmanClientDotNet.Extensions; -using MaksIT.PodmanClientDotNet.Models.Exec; +using Microsoft.Extensions.Logging; + using MaksIT.PodmanClientDotNet.Models; +using MaksIT.PodmanClientDotNet.Models.Exec; +using MaksIT.PodmanClientDotNet.Extensions; + namespace MaksIT.PodmanClientDotNet { public partial class PodmanClient { public async Task CreateExecAsync( - string containerName, - string[] cmd, - bool attachStderr = true, - bool attachStdin = false, - bool attachStdout = true, - string detachKeys = null, - string[] env = null, - bool privileged = false, - bool tty = false, - string user = null, - string workingDir = null - ) { + string containerName, + string[] cmd, + bool attachStderr = true, + bool attachStdin = false, + bool attachStdout = true, + string detachKeys = null, + string[] env = null, + bool privileged = false, + bool tty = false, + string user = null, + string workingDir = null + ) { // Construct the request object var execRequest = new CreateExecRequest { AttachStderr = attachStderr, @@ -40,7 +42,7 @@ namespace MaksIT.PodmanClientDotNet { var content = new StringContent(jsonRequest, Encoding.UTF8, "application/json"); // Create the request URL - var requestUrl = $"/containers/{Uri.EscapeDataString(containerName)}/exec"; + var requestUrl = $"/{_apiVersion}/containers/{Uri.EscapeDataString(containerName)}/exec"; // Send the POST request var response = await _httpClient.PostAsync(requestUrl, content); @@ -51,21 +53,21 @@ namespace MaksIT.PodmanClientDotNet { } else { var jsonResponse = await response.Content.ReadAsStringAsync(); - var errorResponse = JsonSerializer.Deserialize(jsonResponse); + var errorResponse = jsonResponse.ToObject(); // Handle different response codes switch (response.StatusCode) { case System.Net.HttpStatusCode.NotFound: - Console.WriteLine($"No such container: {errorResponse?.Message}"); + _logger.LogInformation($"No such container: {errorResponse?.Message}"); break; case System.Net.HttpStatusCode.Conflict: - Console.WriteLine($"Conflict error: {errorResponse?.Message}"); + _logger.LogInformation($"Conflict error: {errorResponse?.Message}"); break; case System.Net.HttpStatusCode.InternalServerError: - Console.WriteLine($"Internal server error: {errorResponse?.Message}"); + _logger.LogInformation($"Internal server error: {errorResponse?.Message}"); break; default: - Console.WriteLine($"Error creating exec instance: {errorResponse?.Message}"); + _logger.LogInformation($"Error creating exec instance: {errorResponse?.Message}"); break; } @@ -75,16 +77,12 @@ namespace MaksIT.PodmanClientDotNet { } public async Task StartExecAsync( - string execId, - bool detach = false, - bool tty = false, - int? height = null, - int? width = null, - string outputFilePath = "exec_output.log" - ) { - - outputFilePath = Path.Combine(Path.GetTempPath(), outputFilePath); - + string execId, + bool detach = false, + bool tty = false, + int? height = null, + int? width = null +) { // Construct the request object var startExecRequest = new StartExecRequest { Detach = detach, @@ -94,41 +92,37 @@ namespace MaksIT.PodmanClientDotNet { }; // Serialize the request object to JSON - var jsonRequest = JsonSerializer.Serialize(startExecRequest); + var jsonRequest = startExecRequest.ToJson(); var content = new StringContent(jsonRequest, Encoding.UTF8, "application/json"); // Create the request URL - var requestUrl = $"/exec/{Uri.EscapeDataString(execId)}/start"; + var requestUrl = $"/{_apiVersion}/exec/{Uri.EscapeDataString(execId)}/start"; // Send the POST request var response = await _httpClient.PostAsync(requestUrl, content); if (response.IsSuccessStatusCode) { - // Write the response stream directly to a file - using (var responseStream = await response.Content.ReadAsStreamAsync()) - using (var fileStream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write)) { - await responseStream.CopyToAsync(fileStream); - } - var test = File.ReadAllText(outputFilePath); - Console.WriteLine($"Exec instance started and output written to {outputFilePath}"); + var stringResponse = await response.Content.ReadAsStringAsync(); + _logger.LogInformation(stringResponse); + } else { var jsonResponse = await response.Content.ReadAsStringAsync(); - var errorResponse = JsonSerializer.Deserialize(jsonResponse); + var errorResponse = jsonResponse.ToObject(); // Handle different response codes switch (response.StatusCode) { case System.Net.HttpStatusCode.NotFound: - Console.WriteLine($"No such exec instance: {errorResponse?.Message}"); + _logger.LogWarning($"No such exec instance: {errorResponse?.Message}"); break; case System.Net.HttpStatusCode.Conflict: - Console.WriteLine($"Conflict error: {errorResponse?.Message}"); + _logger.LogError($"Conflict error: {errorResponse?.Message}"); break; case System.Net.HttpStatusCode.InternalServerError: - Console.WriteLine($"Internal server error: {errorResponse?.Message}"); + _logger.LogError($"Internal server error: {errorResponse?.Message}"); break; default: - Console.WriteLine($"Error starting exec instance: {errorResponse?.Message}"); + _logger.LogError($"Error starting exec instance: {errorResponse?.Message}"); break; } @@ -136,8 +130,9 @@ namespace MaksIT.PodmanClientDotNet { } } + public async Task InspectExecAsync(string execId) { - var requestUrl = $"/exec/{Uri.EscapeDataString(execId)}/json"; + var requestUrl = $"/{_apiVersion}/exec/{Uri.EscapeDataString(execId)}/json"; var response = await _httpClient.GetAsync(requestUrl); if (response.IsSuccessStatusCode) { @@ -146,19 +141,19 @@ namespace MaksIT.PodmanClientDotNet { } else { var jsonResponse = await response.Content.ReadAsStringAsync(); - var errorResponse = JsonSerializer.Deserialize(jsonResponse); + var errorResponse = jsonResponse.ToObject(); switch (response.StatusCode) { case System.Net.HttpStatusCode.NotFound: - Console.WriteLine($"No such exec instance: {errorResponse?.Message}"); + _logger.LogWarning($"No such exec instance: {errorResponse?.Message}"); return null; case System.Net.HttpStatusCode.InternalServerError: - Console.WriteLine($"Internal server error: {errorResponse?.Message}"); + _logger.LogError($"Internal server error: {errorResponse?.Message}"); break; default: - Console.WriteLine($"Error inspecting exec instance: {errorResponse?.Message}"); + _logger.LogError($"Error inspecting exec instance: {errorResponse?.Message}"); break; } diff --git a/src/PodmanClient/PodmanClientImage.cs b/src/PodmanClient/PodmanClientImage.cs new file mode 100644 index 0000000..1447a75 --- /dev/null +++ b/src/PodmanClient/PodmanClientImage.cs @@ -0,0 +1,98 @@ +using Microsoft.Extensions.Logging; + +using MaksIT.PodmanClientDotNet.Models; +using MaksIT.PodmanClientDotNet.Models.Image; +using MaksIT.PodmanClientDotNet.Extensions; + +namespace MaksIT.PodmanClientDotNet { + public partial class PodmanClient { + + public async Task PullImageAsync(string reference, bool tlsVerify = true, bool quiet = false, string policy = "always", string arch = null, string os = null, string variant = null, bool allTags = false, string authHeader = null) { + var query = $"reference={Uri.EscapeDataString(reference)}&tlsVerify={tlsVerify}&quiet={quiet}&policy={Uri.EscapeDataString(policy)}"; + if (!string.IsNullOrEmpty(arch)) query += $"&Arch={Uri.EscapeDataString(arch)}"; + if (!string.IsNullOrEmpty(os)) query += $"&OS={Uri.EscapeDataString(os)}"; + if (!string.IsNullOrEmpty(variant)) query += $"&Variant={Uri.EscapeDataString(variant)}"; + if (allTags) query += "&allTags=true"; + + if (!string.IsNullOrEmpty(authHeader)) { + _httpClient.DefaultRequestHeaders.Add("X-Registry-Auth", authHeader); + } + + var response = await _httpClient.PostAsync($"/{_apiVersion}/libpod/images/pull?{query}", null); + + if (response.IsSuccessStatusCode) { + + var responseStream = await response.Content.ReadAsStreamAsync(); + using (var reader = new StreamReader(responseStream)) { + string line; + while ((line = await reader.ReadLineAsync()) != null) { + if (line.StartsWith("{\"error\"")) { + var errorDetails = line.ToObject(); + _logger.LogError($"Error pulling image: {errorDetails?.Error}"); + throw new HttpRequestException($"Forced exception: {response.StatusCode} - {errorDetails?.Error}"); + } + } + } + } + else { + var errorContent = await response.Content.ReadAsStringAsync(); + var errorDetails = errorContent.ToObject(); + + switch (response.StatusCode) { + case System.Net.HttpStatusCode.BadRequest: + _logger.LogError($"Bad request: {errorDetails?.Message}"); + break; + + case System.Net.HttpStatusCode.InternalServerError: + _logger.LogError($"Internal server error: {errorDetails?.Message}"); + break; + + default: + _logger.LogError($"Error pulling image: {errorDetails?.Message}"); + break; + } + + response.EnsureSuccessStatusCode(); + } + } + + + public async Task TagImageAsync(string image, string repo, string tag) { + var response = await _httpClient.PostAsync($"/{_apiVersion}/libpod/images/{image}/tag?repo={Uri.EscapeDataString(repo)}&tag={Uri.EscapeDataString(tag)}", null); + + if (response.IsSuccessStatusCode) { + if (response.StatusCode == System.Net.HttpStatusCode.Created) { + _logger.LogInformation("Image tagged successfully."); + } + } + else { + var errorContent = await response.Content.ReadAsStringAsync(); + var errorDetails = errorContent.ToObject(); + + switch (response.StatusCode) { + case System.Net.HttpStatusCode.BadRequest: + _logger.LogError($"Bad parameter in request: {errorDetails?.Message}"); + break; + + case System.Net.HttpStatusCode.NotFound: + _logger.LogWarning($"No such image: {errorDetails?.Message}"); + break; + + case System.Net.HttpStatusCode.Conflict: + _logger.LogError($"Conflict error in operation: {errorDetails?.Message}"); + break; + + case System.Net.HttpStatusCode.InternalServerError: + _logger.LogError($"Internal server error: {errorDetails?.Message}"); + break; + + default: + _logger.LogError($"Error tagging image: {errorDetails?.Message}"); + break; + } + + response.EnsureSuccessStatusCode(); + } + } + } +} diff --git a/src/PodmanClient/PodmanConnector.cs b/src/PodmanClient/PodmanConnector.cs deleted file mode 100644 index ed4e554..0000000 --- a/src/PodmanClient/PodmanConnector.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MaksIT.PodmanClientDotNet { - public partial class PodmanClient { - private readonly HttpClient _httpClient; - - public PodmanClient(string baseAddress, int timeOut) { - _httpClient = new HttpClient(); - _httpClient.BaseAddress = new Uri(baseAddress); - _httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); - _httpClient.Timeout = TimeSpan.FromMinutes(timeOut); - } - - public PodmanClient(HttpClient httpClient) { - _httpClient = httpClient; - _httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); - _httpClient.Timeout = TimeSpan.FromMinutes(60); - } - - } -} diff --git a/src/PodmanClient/PodmanConnectorImage.cs b/src/PodmanClient/PodmanConnectorImage.cs deleted file mode 100644 index 294b574..0000000 --- a/src/PodmanClient/PodmanConnectorImage.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Text.Json; - -using MaksIT.PodmanClientDotNet.Models.Image; -using MaksIT.PodmanClientDotNet.Models; - -namespace MaksIT.PodmanClientDotNet { - public partial class PodmanClient { - - public async Task> PullImageAsync(string reference, bool tlsVerify = true, bool quiet = false, string policy = "always", string arch = null, string os = null, string variant = null, bool allTags = false, string authHeader = null) { - var query = $"reference={Uri.EscapeDataString(reference)}&tlsVerify={tlsVerify}&quiet={quiet}&policy={Uri.EscapeDataString(policy)}"; - if (!string.IsNullOrEmpty(arch)) query += $"&Arch={Uri.EscapeDataString(arch)}"; - if (!string.IsNullOrEmpty(os)) query += $"&OS={Uri.EscapeDataString(os)}"; - if (!string.IsNullOrEmpty(variant)) query += $"&Variant={Uri.EscapeDataString(variant)}"; - if (allTags) query += "&allTags=true"; - - if (!string.IsNullOrEmpty(authHeader)) { - _httpClient.DefaultRequestHeaders.Add("X-Registry-Auth", authHeader); - } - - var response = await _httpClient.PostAsync($"/v1.41/libpod/images/pull?{query}", null); - var imagePullResponses = new List(); - - if (response.IsSuccessStatusCode) { - using var responseStream = await response.Content.ReadAsStreamAsync(); - using var reader = new StreamReader(responseStream); - string line; - - while ((line = await reader.ReadLineAsync()) != null) { - if (line.Contains("\"status\"")) { - // The line contains status information - var statusResponse = JsonSerializer.Deserialize(line); - Console.WriteLine($"Status: {statusResponse.Status}"); - } - else if (line.Contains("\"id\"") || line.Contains("\"images\"")) { - // The line contains image ID information - var imageResponse = JsonSerializer.Deserialize(line); - if (imageResponse != null) { - imagePullResponses.Add(imageResponse); - } - } - } - - return imagePullResponses; - } - else { - var errorContent = await response.Content.ReadAsStringAsync(); - var errorDetails = JsonSerializer.Deserialize(errorContent); - - switch (response.StatusCode) { - case System.Net.HttpStatusCode.BadRequest: - Console.WriteLine($"Bad request: {errorDetails?.Message}"); - break; - - case System.Net.HttpStatusCode.InternalServerError: - Console.WriteLine($"Internal server error: {errorDetails?.Message}"); - break; - - default: - Console.WriteLine($"Error pulling image: {errorDetails?.Message}"); - break; - } - - response.EnsureSuccessStatusCode(); - return null; - } - } - - - public async Task TagImageAsync(string image, string repo, string tag) { - var response = await _httpClient.PostAsync($"/v1.41/libpod/images/{image}/tag?repo={Uri.EscapeDataString(repo)}&tag={Uri.EscapeDataString(tag)}", null); - - if (response.IsSuccessStatusCode) { - if (response.StatusCode == System.Net.HttpStatusCode.Created) { - Console.WriteLine("Image tagged successfully."); - } - } - else { - var errorContent = await response.Content.ReadAsStringAsync(); - var errorDetails = JsonSerializer.Deserialize(errorContent); - - switch (response.StatusCode) { - case System.Net.HttpStatusCode.BadRequest: - Console.WriteLine($"Bad parameter in request: {errorDetails?.Message}"); - break; - - case System.Net.HttpStatusCode.NotFound: - Console.WriteLine($"No such image: {errorDetails?.Message}"); - break; - - case System.Net.HttpStatusCode.Conflict: - Console.WriteLine($"Conflict error in operation: {errorDetails?.Message}"); - break; - - case System.Net.HttpStatusCode.InternalServerError: - Console.WriteLine($"Internal server error: {errorDetails?.Message}"); - break; - - default: - Console.WriteLine($"Error tagging image: {errorDetails?.Message}"); - break; - } - - response.EnsureSuccessStatusCode(); - } - } - } -} diff --git a/src/PodmanClientDotNet.Tests/PodmanClientDotNet.Tests.csproj b/src/PodmanClientDotNet.Tests/PodmanClientDotNet.Tests.csproj new file mode 100644 index 0000000..17f4ec6 --- /dev/null +++ b/src/PodmanClientDotNet.Tests/PodmanClientDotNet.Tests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/src/PodmanClientDotNet.Tests/PodmanClientTests.cs b/src/PodmanClientDotNet.Tests/PodmanClientTests.cs new file mode 100644 index 0000000..b5827be --- /dev/null +++ b/src/PodmanClientDotNet.Tests/PodmanClientTests.cs @@ -0,0 +1,90 @@ + +using Microsoft.Extensions.Logging; +using static System.Net.Mime.MediaTypeNames; + + +namespace MaksIT.PodmanClientDotNet.Tests { + public class PodmanClientIntegrationTests { + private readonly PodmanClient _client; + + public PodmanClientIntegrationTests() { + // Initialize the logger + var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var logger = loggerFactory.CreateLogger(); + + // Initialize PodmanClient with real HttpClient + _client = new PodmanClient(logger, "http://wks0002.corp.maks-it.com:8080", 60); + } + + [Fact] + public async Task PodmanClient_IntegrationTests() { + // Test 1: Pull Image - Success + await PullImageAsync_Should_Succeed(); + + // Test 2: Tag Image - Success + await TagImageAsync_Should_Succeed(); + } + + private async Task PullImageAsync_Should_Succeed() { + // Arrange + string imageReference = "alpine:latest"; // Example image + + // Act + var exception = await Record.ExceptionAsync(() => _client.PullImageAsync(imageReference)); + + // Assert + Assert.Null(exception); // Expect no exceptions if the pull was successful + } + + private async Task TagImageAsync_Should_Succeed() { + // Arrange + string image = "alpine:latest"; // Example image + string repo = "myrepo"; + string tag = "v1"; + + // Act + var exception = await Record.ExceptionAsync(() => _client.TagImageAsync(image, repo, tag)); + + // Assert + Assert.Null(exception); // Expect no exceptions if the tagging was successful + } + + [Fact] + public async Task PodmanClient_PullImage_Errors() { + + // Arrange + string imageReference = "dghdfdghmhgn:latest"; // Intentionally wrong image + + // Act + var exception = await Record.ExceptionAsync(() => _client.PullImageAsync(imageReference)); + + // Assert + Assert.NotNull(exception); // Expect an exception due to nonexistent image + Assert.IsType(exception); // Ensure it's the expected type + } + + [Fact] + public async Task PodmanClient_TagImage_Errors() { + // Arrange + string image = "dghdfdghmhgn:latest"; // Intentionally wrong image + string repo = "myrepo"; + string tag = "v1"; + + // Act + var exception = await Record.ExceptionAsync(() => _client.TagImageAsync(image, repo, tag)); + + // Assert + Assert.NotNull(exception); // Expect an exception due to nonexistent image + Assert.IsType(exception); // Ensure it's the expected type + } + + + + + + + + + + } +} diff --git a/src/PodmanClientDotNet.sln b/src/PodmanClientDotNet.sln index fc4a0ef..b14cb33 100644 --- a/src/PodmanClientDotNet.sln +++ b/src/PodmanClientDotNet.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.9.34902.65 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PodmanClientDotNet", "PodmanClient\PodmanClientDotNet.csproj", "{0833C90F-6BF3-40E4-A035-B6D6C81DB9D7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PodmanClientDotNet.Tests", "PodmanClientDotNet.Tests\PodmanClientDotNet.Tests.csproj", "{657C39AC-BF63-4678-9A35-A782FE9D4D7E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {0833C90F-6BF3-40E4-A035-B6D6C81DB9D7}.Debug|Any CPU.Build.0 = Debug|Any CPU {0833C90F-6BF3-40E4-A035-B6D6C81DB9D7}.Release|Any CPU.ActiveCfg = Release|Any CPU {0833C90F-6BF3-40E4-A035-B6D6C81DB9D7}.Release|Any CPU.Build.0 = Release|Any CPU + {657C39AC-BF63-4678-9A35-A782FE9D4D7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {657C39AC-BF63-4678-9A35-A782FE9D4D7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {657C39AC-BF63-4678-9A35-A782FE9D4D7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {657C39AC-BF63-4678-9A35-A782FE9D4D7E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE