Compare commits
4 Commits
de06e407c5
...
1d14db7a4a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d14db7a4a | ||
|
|
4bb2edcd77 | ||
|
|
b68d8c1bca | ||
|
|
acdcb1300d |
Binary file not shown.
@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace MaksIT.PodmanClientDotNet.Models {
|
||||
public class AutoUserNsOptions {
|
||||
public List<IDMapping> AdditionalGIDMappings { get; set; }
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace MaksIT.PodmanClientDotNet.Models.Container {
|
||||
public class CreateContainerRequest {
|
||||
public Dictionary<string, string> Annotations { get; set; }
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -8,15 +8,21 @@
|
||||
|
||||
<!-- NuGet package metadata -->
|
||||
<PackageId>PodmanClient.DotNet</PackageId>
|
||||
<Version>1.0.2</Version>
|
||||
<Version>1.0.3</Version>
|
||||
<Authors>Maksym Sadovnychyy</Authors>
|
||||
<Company>MAKS-IT</Company>
|
||||
<Product>PodmanClient</Product>
|
||||
<Product>PodmanClient.DotNet</Product>
|
||||
<Description>Podman API client .NET implementation</Description>
|
||||
<PackageTags>podman client dotnet</PackageTags>
|
||||
<RepositoryUrl>https://github.com/MAKS-IT-COM/podman-client-dotnet</RepositoryUrl>
|
||||
<License>MIT</License>
|
||||
<RequireLicenseAcceptance>false</RequireLicenseAcceptance>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<None Include="README.md" Pack="true" />
|
||||
</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;
|
||||
}
|
||||
|
||||
97
src/PodmanClient/PodmanClientImage.cs
Normal file
97
src/PodmanClient/PodmanClientImage.cs
Normal file
@ -0,0 +1,97 @@
|
||||
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<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
139
src/PodmanClient/README.md
Normal file
139
src/PodmanClient/README.md
Normal file
@ -0,0 +1,139 @@
|
||||
# PodmanClient.DotNet
|
||||
|
||||
## Description
|
||||
|
||||
`PodmanClient.DotNet` is a .NET library designed to provide seamless interaction with the Podman API, allowing developers to manage and control containers directly from their .NET applications. This client library wraps the Podman API endpoints, offering a .NET-friendly interface to perform common container operations such as creating, starting, stopping, deleting containers, and more.
|
||||
|
||||
## Purpose
|
||||
|
||||
The primary goal of `PodmanClient.DotNet` is to simplify the integration of Podman into .NET applications by providing a comprehensive, easy-to-use client library. Whether you're managing container lifecycles, executing commands inside containers, or manipulating container images, this library allows developers to interface with Podman using the familiar .NET development environment.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Container Management:** Create, start, stop, and delete containers with straightforward methods.
|
||||
- **Image Operations:** Pull, tag, and manage images using the Podman API.
|
||||
- **Exec Support:** Execute commands inside running containers, with support for attaching input/output streams.
|
||||
- **Volume and Network Management:** Manage container volumes and networks as needed.
|
||||
- **Streamlined Error Handling:** Provides detailed error handling, with informative responses based on HTTP status codes.
|
||||
- **Customizable HTTP Client:** Easily configure and inject your own `HttpClient` instance for extended control and customization.
|
||||
- **Logging Support:** Integrated logging support via `Microsoft.Extensions.Logging` for better observability.
|
||||
|
||||
## Installation
|
||||
|
||||
To include `PodmanClient.DotNet` in your .NET project, you can add the package via NuGet:
|
||||
|
||||
```shell
|
||||
dotnet add package PodmanClient.DotNet
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Initialize the PodmanClient
|
||||
|
||||
```csharp
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MaksIT.PodmanClient.DotNet;
|
||||
|
||||
var logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger<PodmanClient>();
|
||||
var podmanClient = new PodmanClient(logger, "http://localhost:8080", 5);
|
||||
```
|
||||
|
||||
### Create and Start a Container
|
||||
|
||||
```csharp
|
||||
var createResponse = await podmanClient.CreateContainerAsync(
|
||||
name: "my-container",
|
||||
image: "alpine:latest",
|
||||
command: new List<string> { "/bin/sh" },
|
||||
env: new Dictionary<string, string> { { "ENV_VAR", "value" } },
|
||||
remove: true
|
||||
);
|
||||
|
||||
await podmanClient.StartContainerAsync(createResponse.Id);
|
||||
```
|
||||
|
||||
### Execute a Command in a Container
|
||||
|
||||
```csharp
|
||||
var execResponse = await podmanClient.CreateExecAsync(createResponse.Id, new[] { "echo", "Hello, World!" });
|
||||
await podmanClient.StartExecAsync(execResponse.Id);
|
||||
```
|
||||
|
||||
### Pull an Image
|
||||
|
||||
```csharp
|
||||
await podmanClient.PullImageAsync("alpine:latest");
|
||||
```
|
||||
|
||||
### Tag an Image
|
||||
|
||||
```csharp
|
||||
await podmanClient.TagImageAsync("alpine:latest", "myrepo", "mytag");
|
||||
```
|
||||
|
||||
## Available Methods
|
||||
|
||||
### `PodmanClient`
|
||||
|
||||
- **Container Management**
|
||||
- `Task<CreateContainerResponse> CreateContainerAsync(...)`: Creates a new container.
|
||||
- `Task StartContainerAsync(string containerId, string detachKeys = "ctrl-p,ctrl-q")`: Starts an existing container.
|
||||
- `Task StopContainerAsync(string containerId, int timeout = 10, bool ignoreAlreadyStopped = false)`: Stops a running container.
|
||||
- `Task DeleteContainerAsync(string containerId, bool depend = false, bool ignore = false, int timeout = 10)`: Deletes a container.
|
||||
- `Task ForceDeleteContainerAsync(string containerId, bool deleteVolumes = false, int timeout = 10)`: Forcefully deletes a container, optionally removing associated volumes.
|
||||
|
||||
- **Exec Management**
|
||||
- `Task<CreateExecResponse> CreateExecAsync(...)`: Creates an exec instance in a running container.
|
||||
- `Task StartExecAsync(string execId, bool detach = false, bool tty = false, int? height = null, int? width = null)`: Starts an exec instance.
|
||||
- `Task<InspectExecResponse?> InspectExecAsync(string execId)`: Inspects an exec instance to retrieve its details.
|
||||
|
||||
- **Image Operations**
|
||||
- `Task PullImageAsync(...)`: Pulls an image from a container registry.
|
||||
- `Task TagImageAsync(string image, string repo, string tag)`: Tags an existing image with a new repository and tag.
|
||||
|
||||
- **File Operations**
|
||||
- `Task ExtractArchiveToContainerAsync(string containerId, Stream tarStream, string path, bool pause = true)`: Extracts files from a tar stream into a container.
|
||||
|
||||
## Documentation (TODO: Agile)
|
||||
|
||||
For detailed documentation on each method, including parameter descriptions and example usage, please refer to the official documentation (link to be provided).
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions to this project are welcome! Please fork the repository and submit a pull request with your changes. If you encounter any issues or have feature requests, feel free to open an issue on GitHub.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License. See the full license text below.
|
||||
|
||||
---
|
||||
|
||||
### MIT License
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Maksym Sadovnychyy (MAKS-IT)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
## Contact
|
||||
|
||||
For any questions or inquiries, please reach out via GitHub or [email](mailto:maksym.sadovnychyy@gmail.com).
|
||||
54
src/PodmanClientDotNet.Tests/Archives/Tar.cs
Normal file
54
src/PodmanClientDotNet.Tests/Archives/Tar.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using ICSharpCode.SharpZipLib.Tar;
|
||||
|
||||
namespace MaksIT.PodmanClientDotNet.Tests.Archives
|
||||
{
|
||||
public static class Tar
|
||||
{
|
||||
public static void CreateTarFromDirectory(string sourceDirectory, Stream outputStream)
|
||||
{
|
||||
using (var tarOutputStream = new TarOutputStream(outputStream))
|
||||
{
|
||||
tarOutputStream.IsStreamOwner = false;
|
||||
AddDirectoryFilesToTar(tarOutputStream, sourceDirectory, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void AddDirectoryFilesToTar(TarOutputStream tarOutputStream, string sourceDirectory, bool recursive, string baseDirectory = null)
|
||||
{
|
||||
// If baseDirectory is null, set it to the sourceDirectory to start with
|
||||
if (baseDirectory == null)
|
||||
{
|
||||
baseDirectory = sourceDirectory;
|
||||
}
|
||||
|
||||
var directoryInfo = new DirectoryInfo(sourceDirectory);
|
||||
|
||||
foreach (var fileInfo in directoryInfo.GetFiles())
|
||||
{
|
||||
// Calculate the relative path for the file within the base directory
|
||||
string relativePath = Path.GetRelativePath(baseDirectory, fileInfo.FullName);
|
||||
|
||||
// Create tar entry with the relative path
|
||||
var entry = TarEntry.CreateEntryFromFile(fileInfo.FullName);
|
||||
entry.Name = relativePath.Replace(Path.DirectorySeparatorChar, '/'); // Use Unix-style path separators
|
||||
tarOutputStream.PutNextEntry(entry);
|
||||
|
||||
using (var fileStream = fileInfo.OpenRead())
|
||||
{
|
||||
fileStream.CopyTo(tarOutputStream);
|
||||
}
|
||||
|
||||
tarOutputStream.CloseEntry();
|
||||
}
|
||||
|
||||
if (recursive)
|
||||
{
|
||||
foreach (var subDirectory in directoryInfo.GetDirectories())
|
||||
{
|
||||
// Recurse into subdirectories, passing the base directory
|
||||
AddDirectoryFilesToTar(tarOutputStream, subDirectory.FullName, true, baseDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
149
src/PodmanClientDotNet.Tests/PodmanClientContainersTests.cs
Normal file
149
src/PodmanClientDotNet.Tests/PodmanClientContainersTests.cs
Normal file
@ -0,0 +1,149 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using MaksIT.PodmanClientDotNet.Tests.Archives;
|
||||
|
||||
namespace MaksIT.PodmanClientDotNet.Tests {
|
||||
public class PodmanClientContainersTests {
|
||||
private readonly PodmanClient _client;
|
||||
|
||||
public PodmanClientContainersTests() {
|
||||
// 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);
|
||||
}
|
||||
|
||||
#region Success Cases
|
||||
[Fact]
|
||||
public async Task PodmanClient_ContainerLifecycle_Success() {
|
||||
// Arrange
|
||||
string containerName = $"podman-client-test-{Guid.NewGuid()}";
|
||||
string image = "alpine:latest";
|
||||
|
||||
// Act & Assert
|
||||
await PullImageAsync(image);
|
||||
var containerId = await CreateContainerAsync(containerName, image);
|
||||
await StartContainerAsync(containerId);
|
||||
await StopContainerAsync(containerId);
|
||||
await ForceDeleteContainerAsync(containerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CopyFilesToContainer_Success() {
|
||||
// Arrange
|
||||
string containerName = $"podman-client-test-{Guid.NewGuid()}";
|
||||
string image = "alpine:latest";
|
||||
string pathInContainer = "/podman-test-copy";
|
||||
|
||||
// Create temporary folder with random files
|
||||
string tempFolderPath = CreateTemporaryFolderWithFiles();
|
||||
|
||||
try {
|
||||
// Act
|
||||
await PullImageAsync(image);
|
||||
var containerId = await CreateContainerAsync(containerName, image);
|
||||
await StartContainerAsync(containerId);
|
||||
|
||||
// Archive the folder and copy to container
|
||||
using (var tarStream = CreateTarStream(tempFolderPath)) {
|
||||
await CopyToContainerAsync(containerId, tarStream, pathInContainer);
|
||||
}
|
||||
|
||||
// Stop and delete the container
|
||||
await StopContainerAsync(containerId);
|
||||
await ForceDeleteContainerAsync(containerId);
|
||||
}
|
||||
finally {
|
||||
// Cleanup: Delete temporary folder
|
||||
if (Directory.Exists(tempFolderPath)) {
|
||||
Directory.Delete(tempFolderPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
private async Task PullImageAsync(string image) {
|
||||
var exception = await Record.ExceptionAsync(() => _client.PullImageAsync(image));
|
||||
Assert.Null(exception); // Expect no exceptions if the pull was successful
|
||||
}
|
||||
|
||||
private async Task<string> CreateContainerAsync(string containerName, string image) {
|
||||
var createResponse = await _client.CreateContainerAsync(
|
||||
name: containerName,
|
||||
image: image,
|
||||
command: new List<string> {
|
||||
"sh", "-c",
|
||||
"sleep infinity"
|
||||
});
|
||||
Assert.NotNull(createResponse);
|
||||
Assert.False(string.IsNullOrEmpty(createResponse.Id)); // Ensure a valid container ID is returned
|
||||
return createResponse.Id;
|
||||
}
|
||||
|
||||
private async Task StartContainerAsync(string containerId) {
|
||||
var exception = await Record.ExceptionAsync(() => _client.StartContainerAsync(containerId));
|
||||
Assert.Null(exception); // Expect no exceptions if the container was started successfully
|
||||
}
|
||||
|
||||
private async Task StopContainerAsync(string containerId) {
|
||||
var exception = await Record.ExceptionAsync(() => _client.StopContainerAsync(containerId));
|
||||
Assert.Null(exception); // Expect no exceptions if the container was stopped successfully
|
||||
}
|
||||
|
||||
private async Task ForceDeleteContainerAsync(string containerId) {
|
||||
var exception = await Record.ExceptionAsync(() => _client.ForceDeleteContainerAsync(containerId));
|
||||
Assert.Null(exception); // Expect no exceptions if the container was deleted successfully
|
||||
}
|
||||
|
||||
private async Task CopyToContainerAsync(string containerId, Stream tarStream, string path) {
|
||||
var exception = await Record.ExceptionAsync(() => _client.ExtractArchiveToContainerAsync(containerId, tarStream, path));
|
||||
Assert.Null(exception); // Expect no exceptions if the copy was successful
|
||||
}
|
||||
|
||||
private string CreateTemporaryFolderWithFiles() {
|
||||
string tempFolder = Path.Combine(Path.GetTempPath(), $"podman-test-{Guid.NewGuid()}");
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
// Create some random files
|
||||
for (int i = 0; i < 5; i++) {
|
||||
File.WriteAllText(Path.Combine(tempFolder, $"test-file-{i}.txt"), $"This is test file {i}");
|
||||
}
|
||||
|
||||
return tempFolder;
|
||||
}
|
||||
|
||||
private Stream CreateTarStream(string folderPath) {
|
||||
var memoryStream = new MemoryStream();
|
||||
Tar.CreateTarFromDirectory(folderPath, memoryStream);
|
||||
memoryStream.Seek(0, SeekOrigin.Begin); // Reset the stream position for reading
|
||||
return memoryStream;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Fail Cases
|
||||
[Fact]
|
||||
public async Task StartContainerAsync_Should_HandleErrors() {
|
||||
string invalidContainerId = "invalid-container-id";
|
||||
var exception = await Record.ExceptionAsync(() => _client.StartContainerAsync(invalidContainerId));
|
||||
Assert.NotNull(exception); // Expect an exception due to invalid container ID
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StopContainerAsync_Should_HandleErrors() {
|
||||
string invalidContainerId = "invalid-container-id";
|
||||
var exception = await Record.ExceptionAsync(() => _client.StopContainerAsync(invalidContainerId));
|
||||
Assert.NotNull(exception); // Expect an exception due to invalid container ID
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForceDeleteContainerAsync_Should_HandleErrors() {
|
||||
string invalidContainerId = "invalid-container-id";
|
||||
var exception = await Record.ExceptionAsync(() => _client.ForceDeleteContainerAsync(invalidContainerId));
|
||||
Assert.NotNull(exception); // Expect an exception due to invalid container ID
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
35
src/PodmanClientDotNet.Tests/PodmanClientDotNet.Tests.csproj
Normal file
35
src/PodmanClientDotNet.Tests/PodmanClientDotNet.Tests.csproj
Normal file
@ -0,0 +1,35 @@
|
||||
<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="SharpZipLib" Version="1.4.2" />
|
||||
<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>
|
||||
193
src/PodmanClientDotNet.Tests/PodmanClientExecTests.cs
Normal file
193
src/PodmanClientDotNet.Tests/PodmanClientExecTests.cs
Normal file
@ -0,0 +1,193 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit;
|
||||
|
||||
namespace MaksIT.PodmanClientDotNet.Tests {
|
||||
public class PodmanClientExecTests {
|
||||
private readonly PodmanClient _client;
|
||||
|
||||
public PodmanClientExecTests() {
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
|
||||
var logger = loggerFactory.CreateLogger<PodmanClient>();
|
||||
|
||||
_client = new PodmanClient(logger, "http://wks0002.corp.maks-it.com:8080", 60);
|
||||
}
|
||||
|
||||
#region Success Cases
|
||||
|
||||
[Fact]
|
||||
public async Task Full_ContainerLifecycle_With_Exec_Should_Succeed() {
|
||||
// Arrange
|
||||
string containerName = $"podman-client-test-{Guid.NewGuid()}";
|
||||
string image = "alpine:latest";
|
||||
|
||||
// Act & Assert
|
||||
// 1. Pull the image
|
||||
await PullImageAsync(image);
|
||||
|
||||
// 2. Create the container with sleep infinity command
|
||||
var containerId = await CreateContainerAsync(containerName, image);
|
||||
|
||||
// 3. Start the container
|
||||
await StartContainerAsync(containerId);
|
||||
|
||||
// 4. Execute a command in the container to install a package (e.g., "apk add curl")
|
||||
var execId = await CreateExecAsync(containerName, new[] { "apk", "add", "--no-cache", "curl" });
|
||||
await StartExecAsync(execId);
|
||||
|
||||
// 5. Stop the container
|
||||
await StopContainerAsync(containerId);
|
||||
|
||||
// 6. Delete the container
|
||||
await ForceDeleteContainerAsync(containerId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private async Task PullImageAsync(string image) {
|
||||
var exception = await Record.ExceptionAsync(() => _client.PullImageAsync(image));
|
||||
Assert.Null(exception); // Expect no exceptions if the pull was successful
|
||||
}
|
||||
|
||||
private async Task<string> CreateContainerAsync(string containerName, string image) {
|
||||
var createResponse = await _client.CreateContainerAsync(
|
||||
name: containerName,
|
||||
image: image,
|
||||
command: new List<string> {
|
||||
"sh", "-c",
|
||||
"sleep infinity"
|
||||
});
|
||||
Assert.NotNull(createResponse);
|
||||
Assert.False(string.IsNullOrEmpty(createResponse.Id)); // Ensure a valid container ID is returned
|
||||
return createResponse.Id;
|
||||
}
|
||||
|
||||
private async Task StartContainerAsync(string containerId) {
|
||||
var exception = await Record.ExceptionAsync(() => _client.StartContainerAsync(containerId));
|
||||
Assert.Null(exception); // Expect no exceptions if the container was started successfully
|
||||
}
|
||||
|
||||
private async Task<string> CreateExecAsync(string containerName, string[] cmd) {
|
||||
var execResponse = await _client.CreateExecAsync(containerName, cmd);
|
||||
Assert.NotNull(execResponse);
|
||||
Assert.False(string.IsNullOrEmpty(execResponse.Id)); // Ensure a valid exec ID is returned
|
||||
return execResponse.Id;
|
||||
}
|
||||
|
||||
private async Task StartExecAsync(string execId) {
|
||||
var exception = await Record.ExceptionAsync(() => _client.StartExecAsync(execId));
|
||||
Assert.Null(exception); // Expect no exceptions if the exec command was started successfully
|
||||
}
|
||||
|
||||
private async Task StopContainerAsync(string containerId) {
|
||||
var exception = await Record.ExceptionAsync(() => _client.StopContainerAsync(containerId));
|
||||
Assert.Null(exception); // Expect no exceptions if the container was stopped successfully
|
||||
}
|
||||
|
||||
private async Task ForceDeleteContainerAsync(string containerId) {
|
||||
var exception = await Record.ExceptionAsync(() => _client.ForceDeleteContainerAsync(containerId));
|
||||
Assert.Null(exception); // Expect no exceptions if the container was deleted successfully
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fail Cases
|
||||
|
||||
[Fact]
|
||||
public async Task PullImageAsync_Should_HandleErrors() {
|
||||
// Arrange
|
||||
string invalidImageReference = "invalidimage:latest"; // Intentionally wrong image
|
||||
|
||||
// Act
|
||||
var exception = await Record.ExceptionAsync(() => _client.PullImageAsync(invalidImageReference));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(exception);
|
||||
Assert.IsType<HttpRequestException>(exception); // Ensure it's the expected type
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateContainerAsync_Should_HandleErrors() {
|
||||
// Arrange
|
||||
string invalidImage = "invalidimage:latest"; // Intentionally wrong image
|
||||
string containerName = "test-container";
|
||||
|
||||
// Act
|
||||
var exception = await Record.ExceptionAsync(() => _client.CreateContainerAsync(containerName, invalidImage, new List<string> { "sh", "-c", "sleep infinity" }));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(exception);
|
||||
Assert.IsType<HttpRequestException>(exception); // Ensure it's the expected type
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartContainerAsync_Should_HandleErrors() {
|
||||
// Arrange
|
||||
string invalidContainerId = "invalid-container-id"; // Intentionally wrong container ID
|
||||
|
||||
// Act
|
||||
var exception = await Record.ExceptionAsync(() => _client.StartContainerAsync(invalidContainerId));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(exception);
|
||||
Assert.IsType<HttpRequestException>(exception); // Ensure it's the expected type
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateExecAsync_Should_HandleErrors() {
|
||||
// Arrange
|
||||
string containerName = "invalid-container"; // Intentionally wrong container name
|
||||
var cmd = new[] { "apk", "add", "--no-cache", "curl" };
|
||||
|
||||
// Act
|
||||
var exception = await Record.ExceptionAsync(() => _client.CreateExecAsync(containerName, cmd));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(exception);
|
||||
Assert.IsType<HttpRequestException>(exception); // Ensure it's the expected type
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartExecAsync_Should_HandleErrors() {
|
||||
// Arrange
|
||||
string invalidExecId = "invalid-exec-id"; // Intentionally wrong exec ID
|
||||
|
||||
// Act
|
||||
var exception = await Record.ExceptionAsync(() => _client.StartExecAsync(invalidExecId));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(exception);
|
||||
Assert.IsType<HttpRequestException>(exception); // Ensure it's the expected type
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StopContainerAsync_Should_HandleErrors() {
|
||||
// Arrange
|
||||
string invalidContainerId = "invalid-container-id"; // Intentionally wrong container ID
|
||||
|
||||
// Act
|
||||
var exception = await Record.ExceptionAsync(() => _client.StopContainerAsync(invalidContainerId));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(exception);
|
||||
Assert.IsType<HttpRequestException>(exception); // Ensure it's the expected type
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForceDeleteContainerAsync_Should_HandleErrors() {
|
||||
// Arrange
|
||||
string invalidContainerId = "invalid-container-id"; // Intentionally wrong container ID
|
||||
|
||||
// Act
|
||||
var exception = await Record.ExceptionAsync(() => _client.ForceDeleteContainerAsync(invalidContainerId));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(exception);
|
||||
Assert.IsType<HttpRequestException>(exception); // Ensure it's the expected type
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
87
src/PodmanClientDotNet.Tests/PodmanClientImagesTests.cs
Normal file
87
src/PodmanClientDotNet.Tests/PodmanClientImagesTests.cs
Normal file
@ -0,0 +1,87 @@
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
||||
namespace MaksIT.PodmanClientDotNet.Tests;
|
||||
public class PodmanClientImagesTests {
|
||||
private readonly PodmanClient _client;
|
||||
|
||||
public PodmanClientImagesTests() {
|
||||
// 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);
|
||||
}
|
||||
|
||||
#region Success Cases
|
||||
|
||||
[Fact]
|
||||
public async Task PodmanClient_IntegrationTests() {
|
||||
// Test 1: Pull Image - Success
|
||||
await PullImageAsync_Should_Succeed();
|
||||
|
||||
// Test 2: Tag Image - Success
|
||||
await TagImageAsync_Should_Succeed();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
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
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Fail Cases
|
||||
[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
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -1,92 +0,0 @@
|
||||
# PodmanClientDotNet
|
||||
|
||||
## Description
|
||||
|
||||
`PodmanClientDotNet` is a .NET library designed to provide seamless interaction with the Podman API, allowing developers to manage and control containers directly from their .NET applications. This client library wraps the Podman API endpoints, offering a .NET-friendly interface to perform common container operations such as creating, starting, stopping, deleting containers, and more.
|
||||
|
||||
## Purpose
|
||||
|
||||
The primary goal of `PodmanClientDotNet` is to simplify the integration of Podman into .NET applications by providing a comprehensive, easy-to-use client library. Whether you're managing container lifecycles, executing commands inside containers, or manipulating container images, this library allows developers to interface with Podman using the familiar .NET development environment.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Container Management:** Create, start, stop, and delete containers with straightforward methods.
|
||||
- **Image Operations:** Pull, tag, and manage images using the Podman API.
|
||||
- **Exec Support:** Execute commands inside running containers, with support for attaching input/output streams.
|
||||
- **Volume and Network Management:** Manage container volumes and networks as needed.
|
||||
- **Streamlined Error Handling:** Provides detailed error handling, with informative responses based on HTTP status codes.
|
||||
|
||||
## Usage Example
|
||||
|
||||
```csharp
|
||||
// Initialize the PodmanClient with base URL and timeout
|
||||
var podmanClient = new PodmanClient("http://localhost:8080", 5);
|
||||
|
||||
// Create a new container
|
||||
var createResponse = await podmanClient.CreateContainerAsync(
|
||||
name: "my-container",
|
||||
image: "alpine:latest",
|
||||
command: new List<string> { "/bin/sh" },
|
||||
env: new Dictionary<string, string> { { "ENV_VAR", "value" } },
|
||||
remove: true
|
||||
);
|
||||
|
||||
// Start the container
|
||||
await podmanClient.StartContainerAsync(createResponse.Id);
|
||||
|
||||
// Execute a command inside the container
|
||||
var execResponse = await podmanClient.CreateExecAsync(createResponse.Id, new[] { "echo", "Hello, World!" });
|
||||
await podmanClient.StartExecAsync(execResponse.Id);
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
To include `PodmanClientDotNet` in your .NET project, you can add the package via NuGet:
|
||||
|
||||
```shell
|
||||
dotnet add package PodmanClientDotNet
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For detailed documentation on each method, including parameter descriptions and example usage, please refer to the official documentation (link to be provided).
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions to this project are welcome! Please fork the repository and submit a pull request with your changes. If you encounter any issues or have feature requests, feel free to open an issue on GitHub.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License. See the full license text below.
|
||||
|
||||
---
|
||||
|
||||
### MIT License
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Maksym Sadovnychyy (MAKS-IT)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
## Contact
|
||||
|
||||
For any questions or inquiries, please reach out via GitHub or [email](mailto:maksym.sadovnychyy@gmail.com).
|
||||
Loading…
Reference in New Issue
Block a user