(refactor): code clean up, image pull and tag test
This commit is contained in:
parent
de06e407c5
commit
a0efb59c7c
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
32
src/PodmanClient/Models/Image/PullImageResponse.cs
Normal file
32
src/PodmanClient/Models/Image/PullImageResponse.cs
Normal file
@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Error contains text of errors from c/image.
|
||||
/// </summary>
|
||||
public string Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID contains image ID (retained for backwards compatibility).
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Images contains the IDs of the images pulled.
|
||||
/// </summary>
|
||||
public List<string> Images { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stream used to provide output from c/image.
|
||||
/// </summary>
|
||||
public string Stream { get; set; }
|
||||
}
|
||||
}
|
||||
61
src/PodmanClient/PodmanClient.cs
Normal file
61
src/PodmanClient/PodmanClient.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MaksIT.PodmanClientDotNet {
|
||||
public partial class PodmanClient {
|
||||
private readonly ILogger<PodmanClient> _logger;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
private const string _apiVersion = "v1.41";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PodmanClient"/> class using the specified base address and timeout.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger instance used for logging within the client.</param>
|
||||
/// <param name="baseAddress">The base address of the Podman service.</param>
|
||||
/// <param name="timeOut">The timeout period in minutes for HTTP requests.</param>
|
||||
public PodmanClient(
|
||||
ILogger<PodmanClient> logger,
|
||||
string baseAddress,
|
||||
int timeOut = 60
|
||||
) : this(
|
||||
logger,
|
||||
baseAddress,
|
||||
new HttpClient {
|
||||
Timeout = TimeSpan.FromMinutes(timeOut)
|
||||
}
|
||||
) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PodmanClient"/> class using the provided <see cref="HttpClient"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger instance used for logging within the client.</param>
|
||||
/// <param name="httpClient">An existing <see cref="HttpClient"/> instance configured for use with the Podman service.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the logger or httpClient parameter is null.</exception>
|
||||
public PodmanClient(
|
||||
ILogger<PodmanClient> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the default settings for the <see cref="HttpClient"/> used by this instance.
|
||||
/// Ensures that the "Accept" header is set to "application/json".
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<CreateContainerResponse>(jsonResponse);
|
||||
return jsonResponse.ToObject<CreateContainerResponse>();
|
||||
}
|
||||
else {
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
var errorDetails = JsonSerializer.Deserialize<ErrorResponse>(errorContent);
|
||||
var errorDetails = errorContent.ToObject<ErrorResponse>();
|
||||
|
||||
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<ErrorResponse>();
|
||||
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<ErrorResponse>();
|
||||
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<ErrorResponse>();
|
||||
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<DeleteContainerResponse[]>(responseContent);
|
||||
var deleteResponses = responseContent.ToObject<DeleteContainerResponse[]>();
|
||||
|
||||
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<ErrorResponse>(errorContent);
|
||||
var errorDetails = errorContent.ToObject<ErrorResponse>();
|
||||
|
||||
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<ErrorResponse>(errorContent);
|
||||
var errorDetails = errorContent.ToObject<ErrorResponse>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -19,4 +19,8 @@
|
||||
<RequireLicenseAcceptance>false</RequireLicenseAcceptance>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -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<CreateExecResponse> 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<ErrorResponse>(jsonResponse);
|
||||
var errorResponse = jsonResponse.ToObject<ErrorResponse>();
|
||||
|
||||
// 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<ErrorResponse>(jsonResponse);
|
||||
var errorResponse = jsonResponse.ToObject<ErrorResponse>();
|
||||
|
||||
// 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<InspectExecResponse?> 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<ErrorResponse>(jsonResponse);
|
||||
var errorResponse = jsonResponse.ToObject<ErrorResponse>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
100
src/PodmanClient/PodmanClientImage.cs
Normal file
100
src/PodmanClient/PodmanClientImage.cs
Normal file
@ -0,0 +1,100 @@
|
||||
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) {
|
||||
// Process each line as needed
|
||||
// For example, you can accumulate the lines into a single string or parse them directly
|
||||
if (line.StartsWith("{\"error\"")) {
|
||||
var errorDetails = line.ToObject<PullImageResponse>();
|
||||
_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<ErrorResponse>();
|
||||
|
||||
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<ErrorResponse>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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<List<ImagePullStatusResponse>> 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<ImagePullStatusResponse>();
|
||||
|
||||
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<ImagePullStatusResponse>(line);
|
||||
Console.WriteLine($"Status: {statusResponse.Status}");
|
||||
}
|
||||
else if (line.Contains("\"id\"") || line.Contains("\"images\"")) {
|
||||
// The line contains image ID information
|
||||
var imageResponse = JsonSerializer.Deserialize<ImagePullStatusResponse>(line);
|
||||
if (imageResponse != null) {
|
||||
imagePullResponses.Add(imageResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imagePullResponses;
|
||||
}
|
||||
else {
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
var errorDetails = JsonSerializer.Deserialize<ErrorResponse>(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<ErrorResponse>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/PodmanClientDotNet.Tests/PodmanClientDotNet.Tests.csproj
Normal file
34
src/PodmanClientDotNet.Tests/PodmanClientDotNet.Tests.csproj
Normal file
@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PodmanClient\PodmanClientDotNet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
90
src/PodmanClientDotNet.Tests/PodmanClientTests.cs
Normal file
90
src/PodmanClientDotNet.Tests/PodmanClientTests.cs
Normal file
@ -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<PodmanClient>();
|
||||
|
||||
// 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<HttpRequestException>(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<HttpRequestException>(exception); // Ensure it's the expected type
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user