(feature): add native aot and trim compatibility via system.text.json source generation

Co-authored-by: Benjamin Bartels <benjamin@bartels.dev>
This commit is contained in:
Maksym Sadovnychyy 2026-07-02 18:15:43 +02:00
parent b4a497ec47
commit 94aca92d5d
18 changed files with 224 additions and 62 deletions

View File

@ -1,8 +1,8 @@
using System.Net;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using MaksIT.Core.Extensions;
using MaksIT.PodmanClientDotNet.Dtos.Common;
using MaksIT.Results;
@ -13,7 +13,7 @@ internal static class PodmanHttpResults {
if (string.IsNullOrWhiteSpace(content))
return "Podman API request failed.";
var error = content.ToObject<ErrorResponseDto>();
var error = JsonSerializer.Deserialize(content, PodmanJsonContext.Default.ErrorResponseDto);
return string.IsNullOrWhiteSpace(error?.Message) ? content : error.Message;
}

View File

@ -1,6 +1,7 @@
using System.Text.Json;
using Microsoft.Extensions.Logging;
using MaksIT.Core.Extensions;
using MaksIT.PodmanClientDotNet.Dtos.Build;
using MaksIT.PodmanClientDotNet.Dtos.Image;
using MaksIT.Results;
@ -24,7 +25,7 @@ internal static class PodmanNdjsonStreams {
if (!line.Contains("\"error\"", StringComparison.Ordinal))
continue;
var errorDetails = line.ToObject<PullImageResponseDto>();
var errorDetails = JsonSerializer.Deserialize(line, PodmanJsonContext.Default.PullImageResponseDto);
var message = errorDetails?.Error ?? $"{operation} failed.";
logger.LogError("{Operation} failed: {Message}", operation, message);
return Result.BadRequest(message);
@ -46,7 +47,7 @@ internal static class PodmanNdjsonStreams {
if (string.IsNullOrWhiteSpace(line))
continue;
var progress = line.ToObject<BuildProgressLineDto>();
var progress = JsonSerializer.Deserialize(line, PodmanJsonContext.Default.BuildProgressLineDto);
if (progress is null)
continue;

View File

@ -1,8 +1,9 @@
using MaksIT.PodmanClientDotNet;
using System.Net;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using MaksIT.Core.Extensions;
using MaksIT.PodmanClientDotNet.Internal;
using MaksIT.PodmanClientDotNet.Models;
using MaksIT.PodmanClientDotNet.Dtos.Container;
@ -251,7 +252,9 @@ public partial class PodmanClient {
return await PostJsonAsync<CreateContainerRequest, CreateContainerResponseDto>(
"/libpod/containers/create",
"Create container",
createContainerParameters
createContainerParameters,
PodmanJsonContext.Default.CreateContainerRequest,
PodmanJsonContext.Default.CreateContainerResponseDto
).ConfigureAwait(false);
}
@ -314,7 +317,7 @@ public partial class PodmanClient {
if (response.IsSuccessStatusCode) {
var value = !string.IsNullOrWhiteSpace(jsonResponse)
? jsonResponse.ToObject<DeleteContainerResponseDto[]>()
? JsonSerializer.Deserialize(jsonResponse, PodmanJsonContext.Default.DeleteContainerResponseDtoArray)
: null;
return PodmanHttpResults.Success(response.StatusCode, value);
}
@ -336,7 +339,7 @@ public partial class PodmanClient {
if (response.IsSuccessStatusCode) {
var value = !string.IsNullOrWhiteSpace(jsonResponse)
? jsonResponse.ToObject<DeleteContainerResponseDto[]>()
? JsonSerializer.Deserialize(jsonResponse, PodmanJsonContext.Default.DeleteContainerResponseDtoArray)
: null;
return PodmanHttpResults.Success(response.StatusCode, value);
}

View File

@ -1,3 +1,4 @@
using MaksIT.PodmanClientDotNet;
using MaksIT.PodmanClientDotNet.Dtos.Common;
using MaksIT.PodmanClientDotNet.Dtos.Container;
using MaksIT.Results;
@ -16,6 +17,7 @@ public partial class PodmanClient {
GetJsonAsync<List<ContainerListEntryDto>>(
"/libpod/containers/json",
"List containers",
PodmanJsonContext.Default.ListContainerListEntryDto,
[
("all", all.ToString().ToLowerInvariant()),
("limit", limit?.ToString()),
@ -27,7 +29,7 @@ public partial class PodmanClient {
);
public Task<Result<ContainerInspectDto?>> InspectContainerAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<ContainerInspectDto>($"{ContainerPath(name)}/json", "Inspect container", cancellationToken: cancellationToken);
GetJsonAsync<ContainerInspectDto>($"{ContainerPath(name)}/json", "Inspect container", PodmanJsonContext.Default.ContainerInspectDto, cancellationToken: cancellationToken);
public Task<Result> ContainerExistsAsync(string name, CancellationToken cancellationToken = default) =>
GetWithoutBodyAsync($"{ContainerPath(name)}/exists", "Container exists", cancellationToken: cancellationToken);
@ -58,6 +60,7 @@ public partial class PodmanClient {
PostLibpodAsync<ContainerWaitDto>(
$"{ContainerPath(name)}/wait",
"Wait container",
PodmanJsonContext.Default.ContainerWaitDto,
query: condition is null ? null : [("condition", condition)],
cancellationToken: cancellationToken
);
@ -92,6 +95,7 @@ public partial class PodmanClient {
GetJsonAsync<ContainerStatsDto>(
$"{ContainerPath(name)}/stats",
"Get container stats",
PodmanJsonContext.Default.ContainerStatsDto,
[("stream", stream.ToString().ToLowerInvariant())],
cancellationToken
);
@ -107,13 +111,14 @@ public partial class PodmanClient {
query.Add(("containers", c));
}
return GetJsonAsync<Dictionary<string, ContainerStatsDto>>("/libpod/containers/stats", "Get containers stats", query, cancellationToken);
return GetJsonAsync<Dictionary<string, ContainerStatsDto>>("/libpod/containers/stats", "Get containers stats", PodmanJsonContext.Default.DictionaryStringContainerStatsDto, query, cancellationToken);
}
public Task<Result<PruneReportDto?>> PruneContainersAsync(string? filters = null, CancellationToken cancellationToken = default) =>
PostLibpodAsync<PruneReportDto>(
"/libpod/containers/prune",
"Prune containers",
PodmanJsonContext.Default.PruneReportDto,
query: filters is null ? null : [("filters", filters)],
cancellationToken: cancellationToken
);
@ -178,7 +183,7 @@ public partial class PodmanClient {
);
public Task<Result<ContainerMountDto?>> MountContainerAsync(string name, CancellationToken cancellationToken = default) =>
PostLibpodAsync<ContainerMountDto>($"{ContainerPath(name)}/mount", "Mount container", cancellationToken: cancellationToken);
PostLibpodAsync<ContainerMountDto>($"{ContainerPath(name)}/mount", "Mount container", PodmanJsonContext.Default.ContainerMountDto, cancellationToken: cancellationToken);
public Task<Result> UnmountContainerAsync(string name, CancellationToken cancellationToken = default) =>
PostWithoutBodyAsync($"{ContainerPath(name)}/unmount", "Unmount container", cancellationToken: cancellationToken);
@ -228,7 +233,7 @@ public partial class PodmanClient {
);
public Task<Result<ContainerChangesDto?>> GetContainerChangesAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<ContainerChangesDto>($"{ContainerPath(name)}/changes", "Get container changes", cancellationToken: cancellationToken);
GetJsonAsync<ContainerChangesDto>($"{ContainerPath(name)}/changes", "Get container changes", PodmanJsonContext.Default.ContainerChangesDto, cancellationToken: cancellationToken);
public Task<Result<ContainerCommitDto?>> CommitContainerAsync(
string container,
@ -255,14 +260,14 @@ public partial class PodmanClient {
query.Add(("changes", change));
}
return PostLibpodAsync<ContainerCommitDto>("/libpod/commit", "Commit container", query: query, cancellationToken: cancellationToken);
return PostLibpodAsync<ContainerCommitDto>("/libpod/commit", "Commit container", PodmanJsonContext.Default.ContainerCommitDto, query: query, cancellationToken: cancellationToken);
}
public Task<Result<ContainerHealthCheckDto?>> HealthCheckContainerAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<ContainerHealthCheckDto>($"{ContainerPath(name)}/healthcheck", "Health check container", cancellationToken: cancellationToken);
GetJsonAsync<ContainerHealthCheckDto>($"{ContainerPath(name)}/healthcheck", "Health check container", PodmanJsonContext.Default.ContainerHealthCheckDto, cancellationToken: cancellationToken);
public Task<Result<MountedContainersResponseDto?>> GetMountedContainersAsync(CancellationToken cancellationToken = default) =>
GetJsonAsync<MountedContainersResponseDto>("/libpod/containers/showmounted", "Get mounted containers", cancellationToken: cancellationToken);
GetJsonAsync<MountedContainersResponseDto>("/libpod/containers/showmounted", "Get mounted containers", PodmanJsonContext.Default.MountedContainersResponseDto, cancellationToken: cancellationToken);
public Task<Result<ContainerTopDto?>> TopContainerAsync(
string name,
@ -273,6 +278,7 @@ public partial class PodmanClient {
GetJsonAsync<ContainerTopDto>(
$"{ContainerPath(name)}/top",
"Top container",
PodmanJsonContext.Default.ContainerTopDto,
[
("ps_args", psArgs),
("stream", stream.ToString().ToLowerInvariant()),

View File

@ -1,8 +1,8 @@
using System.Text;
using MaksIT.PodmanClientDotNet;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using MaksIT.Core.Extensions;
using MaksIT.PodmanClientDotNet.Internal;
using MaksIT.PodmanClientDotNet.Models;
using MaksIT.PodmanClientDotNet.Dtos.Exec;
@ -39,7 +39,9 @@ public partial class PodmanClient {
return await PostJsonAsync<CreateExecRequest, CreateExecResponseDto>(
$"/libpod/containers/{Uri.EscapeDataString(containerName)}/exec",
"Create exec",
execRequest
execRequest,
PodmanJsonContext.Default.CreateExecRequest,
PodmanJsonContext.Default.CreateExecResponseDto
).ConfigureAwait(false);
}
@ -60,7 +62,8 @@ public partial class PodmanClient {
var result = await PostJsonWithoutBodyAsync<StartExecRequest>(
$"/libpod/exec/{Uri.EscapeDataString(execId)}/start",
"Start exec",
startExecRequest
startExecRequest,
PodmanJsonContext.Default.StartExecRequest
).ConfigureAwait(false);
if (result.IsSuccess)
@ -72,7 +75,8 @@ public partial class PodmanClient {
public Task<Result<InspectExecResponseDto?>> InspectExecAsync(string execId) =>
GetJsonAsync<InspectExecResponseDto>(
$"/libpod/exec/{Uri.EscapeDataString(execId)}/json",
"Inspect exec"
"Inspect exec",
PodmanJsonContext.Default.InspectExecResponseDto
);
public Task<Result> ResizeExecAsync(string execId, int height, int width, CancellationToken cancellationToken = default) =>

View File

@ -1,3 +1,4 @@
using MaksIT.PodmanClientDotNet;
using System.Net.Http.Headers;
using MaksIT.PodmanClientDotNet.Dtos.Generate;
@ -18,6 +19,7 @@ public partial class PodmanClient {
GetJsonAsync<GenerateSystemdDto>(
$"/libpod/generate/{Uri.EscapeDataString(name)}/systemd",
"Generate systemd",
PodmanJsonContext.Default.GenerateSystemdDto,
[
("useName", useName.ToString().ToLowerInvariant()),
("new", createNew.ToString().ToLowerInvariant()),
@ -62,6 +64,7 @@ public partial class PodmanClient {
return PostLibpodAsync<PlayKubeReportDto>(
"/libpod/play/kube",
"Play kube",
PodmanJsonContext.Default.PlayKubeReportDto,
content,
[
("network", network),

View File

@ -1,7 +1,8 @@
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using MaksIT.Core.Extensions;
using MaksIT.PodmanClientDotNet.Internal;
using MaksIT.Results;
@ -55,13 +56,14 @@ public partial class PodmanClient {
internal Task<Result<T?>> GetJsonAsync<T>(
string libpodPath,
string operation,
JsonTypeInfo<T> typeInfo,
IEnumerable<(string Key, string? Value)>? query = null,
CancellationToken cancellationToken = default
) =>
SendAsync<T>(
() => _httpClient.GetAsync(LibpodPath(libpodPath) + BuildQuery(query ?? []), cancellationToken),
operation,
body => body.ToObject<T>(),
body => JsonSerializer.Deserialize(body, typeInfo),
cancellationToken
);
@ -81,17 +83,19 @@ public partial class PodmanClient {
string libpodPath,
string operation,
TRequest? requestBody,
JsonTypeInfo<TRequest> requestTypeInfo,
JsonTypeInfo<TResponse> responseTypeInfo,
IEnumerable<(string Key, string? Value)>? query = null,
CancellationToken cancellationToken = default
) {
var content = requestBody is null
? null
: new StringContent(requestBody.ToJson(), Encoding.UTF8, "application/json");
: new StringContent(JsonSerializer.Serialize(requestBody, requestTypeInfo), Encoding.UTF8, "application/json");
return SendAsync<TResponse>(
() => _httpClient.PostAsync(LibpodPath(libpodPath) + BuildQuery(query ?? []), content, cancellationToken),
operation,
body => string.IsNullOrWhiteSpace(body) ? default : body.ToObject<TResponse>(),
body => string.IsNullOrWhiteSpace(body) ? default : JsonSerializer.Deserialize(body, responseTypeInfo),
cancellationToken
);
}
@ -100,12 +104,13 @@ public partial class PodmanClient {
string libpodPath,
string operation,
TRequest? requestBody,
JsonTypeInfo<TRequest> requestTypeInfo,
IEnumerable<(string Key, string? Value)>? query = null,
CancellationToken cancellationToken = default
) {
var content = requestBody is null
? null
: new StringContent(requestBody.ToJson(), Encoding.UTF8, "application/json");
: new StringContent(JsonSerializer.Serialize(requestBody, requestTypeInfo), Encoding.UTF8, "application/json");
return SendWithoutBodyAsync(
() => _httpClient.PostAsync(LibpodPath(libpodPath) + BuildQuery(query ?? []), content, cancellationToken),
@ -155,6 +160,7 @@ public partial class PodmanClient {
internal Task<Result<TResponse?>> PostLibpodAsync<TResponse>(
string libpodPath,
string operation,
JsonTypeInfo<TResponse> responseTypeInfo,
HttpContent? content = null,
IEnumerable<(string Key, string? Value)>? query = null,
CancellationToken cancellationToken = default
@ -162,20 +168,21 @@ public partial class PodmanClient {
SendAsync<TResponse>(
() => _httpClient.PostAsync(LibpodPath(libpodPath) + BuildQuery(query ?? []), content, cancellationToken),
operation,
body => string.IsNullOrWhiteSpace(body) ? default : body.ToObject<TResponse>(),
body => string.IsNullOrWhiteSpace(body) ? default : JsonSerializer.Deserialize(body, responseTypeInfo),
cancellationToken
);
internal Task<Result<T?>> DeleteJsonAsync<T>(
string libpodPath,
string operation,
JsonTypeInfo<T> typeInfo,
IEnumerable<(string Key, string? Value)>? query = null,
CancellationToken cancellationToken = default
) =>
SendAsync<T>(
() => _httpClient.DeleteAsync(LibpodPath(libpodPath) + BuildQuery(query ?? []), cancellationToken),
operation,
body => string.IsNullOrWhiteSpace(body) ? default : body.ToObject<T>(),
body => string.IsNullOrWhiteSpace(body) ? default : JsonSerializer.Deserialize(body, typeInfo),
cancellationToken
);

View File

@ -1,3 +1,4 @@
using MaksIT.PodmanClientDotNet;
using System.Net.Http.Headers;
using MaksIT.PodmanClientDotNet.Dtos.Common;
@ -16,6 +17,7 @@ public partial class PodmanClient {
GetJsonAsync<List<ImageListEntryDto>>(
"/libpod/images/json",
"List images",
PodmanJsonContext.Default.ListImageListEntryDto,
[
("all", all.ToString().ToLowerInvariant()),
("filters", filters),
@ -24,7 +26,7 @@ public partial class PodmanClient {
);
public Task<Result<ImageInspectDto?>> InspectImageAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<ImageInspectDto>($"{ImagePath(name)}/json", "Inspect image", cancellationToken: cancellationToken);
GetJsonAsync<ImageInspectDto>($"{ImagePath(name)}/json", "Inspect image", PodmanJsonContext.Default.ImageInspectDto, cancellationToken: cancellationToken);
public Task<Result> ImageExistsAsync(string name, CancellationToken cancellationToken = default) =>
GetWithoutBodyAsync($"{ImagePath(name)}/exists", "Image exists", cancellationToken: cancellationToken);
@ -33,6 +35,7 @@ public partial class PodmanClient {
DeleteJsonAsync<ImageDeleteDto[]>(
ImagePath(name),
"Delete image",
PodmanJsonContext.Default.ImageDeleteDtoArray,
[("force", force.ToString().ToLowerInvariant())],
cancellationToken
);
@ -50,11 +53,11 @@ public partial class PodmanClient {
foreach (var image in images)
query.Add(("images", image));
return DeleteJsonAsync<ImageDeleteDto[]>("/libpod/images/remove", "Remove images", query, cancellationToken);
return DeleteJsonAsync<ImageDeleteDto[]>("/libpod/images/remove", "Remove images", PodmanJsonContext.Default.ImageDeleteDtoArray, query, cancellationToken);
}
public Task<Result<PruneReportDto?>> PruneImagesAsync(CancellationToken cancellationToken = default) =>
PostLibpodAsync<PruneReportDto>("/libpod/images/prune", "Prune images", cancellationToken: cancellationToken);
PostLibpodAsync<PruneReportDto>("/libpod/images/prune", "Prune images", PodmanJsonContext.Default.PruneReportDto, cancellationToken: cancellationToken);
public Task<Result<List<ImageSearchResultDto>?>> SearchImagesAsync(
string term,
@ -64,6 +67,7 @@ public partial class PodmanClient {
GetJsonAsync<List<ImageSearchResultDto>>(
"/libpod/images/search",
"Search images",
PodmanJsonContext.Default.ListImageSearchResultDto,
[
("term", term),
("limit", limit?.ToString()),
@ -115,13 +119,13 @@ public partial class PodmanClient {
);
public Task<Result<List<ImageHistoryEntryDto>?>> GetImageHistoryAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<List<ImageHistoryEntryDto>>($"{ImagePath(name)}/history", "Get image history", cancellationToken: cancellationToken);
GetJsonAsync<List<ImageHistoryEntryDto>>($"{ImagePath(name)}/history", "Get image history", PodmanJsonContext.Default.ListImageHistoryEntryDto, cancellationToken: cancellationToken);
public Task<Result<ImageTreeDto?>> GetImageTreeAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<ImageTreeDto>($"{ImagePath(name)}/tree", "Get image tree", cancellationToken: cancellationToken);
GetJsonAsync<ImageTreeDto>($"{ImagePath(name)}/tree", "Get image tree", PodmanJsonContext.Default.ImageTreeDto, cancellationToken: cancellationToken);
public Task<Result<ImageChangesDto?>> GetImageChangesAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<ImageChangesDto>($"{ImagePath(name)}/changes", "Get image changes", cancellationToken: cancellationToken);
GetJsonAsync<ImageChangesDto>($"{ImagePath(name)}/changes", "Get image changes", PodmanJsonContext.Default.ImageChangesDto, cancellationToken: cancellationToken);
public Task<Result<ImageImportDto?>> ImportImageAsync(
Stream? tarball = null,
@ -140,6 +144,7 @@ public partial class PodmanClient {
return PostLibpodAsync<ImageImportDto>(
"/libpod/images/import",
"Import image",
PodmanJsonContext.Default.ImageImportDto,
content,
[
("changes", changes),
@ -154,7 +159,7 @@ public partial class PodmanClient {
public Task<Result<ImageLoadDto?>> LoadImageAsync(Stream tarball, CancellationToken cancellationToken = default) {
var content = new StreamContent(tarball);
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-tar");
return PostLibpodAsync<ImageLoadDto>("/libpod/images/load", "Load image", content, cancellationToken: cancellationToken);
return PostLibpodAsync<ImageLoadDto>("/libpod/images/load", "Load image", PodmanJsonContext.Default.ImageLoadDto, content, cancellationToken: cancellationToken);
}
public Task<Result<Stream?>> ExportImagesAsync(

View File

@ -1,3 +1,4 @@
using MaksIT.PodmanClientDotNet;
using MaksIT.PodmanClientDotNet.Dtos.Manifest;
using MaksIT.Results;
@ -13,6 +14,7 @@ public partial class PodmanClient {
PostLibpodAsync<ManifestCreateDto>(
"/libpod/manifests/create",
"Create manifest",
PodmanJsonContext.Default.ManifestCreateDto,
query: [
("name", name),
("image", image),
@ -30,10 +32,10 @@ public partial class PodmanClient {
);
public Task<Result<ManifestInspectDto?>> InspectManifestAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<ManifestInspectDto>($"{ManifestPath(name)}/json", "Inspect manifest", cancellationToken: cancellationToken);
GetJsonAsync<ManifestInspectDto>($"{ManifestPath(name)}/json", "Inspect manifest", PodmanJsonContext.Default.ManifestInspectDto, cancellationToken: cancellationToken);
public Task<Result> AddToManifestAsync(string name, ManifestAddRequestDto request, CancellationToken cancellationToken = default) =>
PostJsonWithoutBodyAsync($"{ManifestPath(name)}/add", "Add to manifest", request, cancellationToken: cancellationToken);
PostJsonWithoutBodyAsync($"{ManifestPath(name)}/add", "Add to manifest", request, PodmanJsonContext.Default.ManifestAddRequestDto, cancellationToken: cancellationToken);
public Task<Result> PushManifestAsync(
string name,

View File

@ -1,3 +1,4 @@
using MaksIT.PodmanClientDotNet;
using MaksIT.PodmanClientDotNet.Dtos.Network;
using MaksIT.PodmanClientDotNet.Models.Network;
using MaksIT.Results;
@ -11,14 +12,16 @@ public partial class PodmanClient {
"/libpod/networks/create",
"Create network",
request,
PodmanJsonContext.Default.NetworkCreateRequest,
PodmanJsonContext.Default.NetworkListEntryDto,
cancellationToken: cancellationToken
);
public Task<Result<List<NetworkListEntryDto>?>> ListNetworksAsync(CancellationToken cancellationToken = default) =>
GetJsonAsync<List<NetworkListEntryDto>>("/libpod/networks/json", "List networks", cancellationToken: cancellationToken);
GetJsonAsync<List<NetworkListEntryDto>>("/libpod/networks/json", "List networks", PodmanJsonContext.Default.ListNetworkListEntryDto, cancellationToken: cancellationToken);
public Task<Result<NetworkInspectDto?>> InspectNetworkAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<NetworkInspectDto>($"/libpod/networks/{Uri.EscapeDataString(name)}/json", "Inspect network", cancellationToken: cancellationToken);
GetJsonAsync<NetworkInspectDto>($"/libpod/networks/{Uri.EscapeDataString(name)}/json", "Inspect network", PodmanJsonContext.Default.NetworkInspectDto, cancellationToken: cancellationToken);
public Task<Result> DeleteNetworkAsync(string name, CancellationToken cancellationToken = default) =>
DeleteWithoutBodyAsync($"/libpod/networks/{Uri.EscapeDataString(name)}", "Delete network", cancellationToken: cancellationToken);
@ -32,6 +35,7 @@ public partial class PodmanClient {
$"/libpod/networks/{Uri.EscapeDataString(name)}/connect",
"Connect network",
request,
PodmanJsonContext.Default.NetworkConnectRequest,
cancellationToken: cancellationToken
);
@ -44,6 +48,7 @@ public partial class PodmanClient {
$"/libpod/networks/{Uri.EscapeDataString(name)}/disconnect",
"Disconnect network",
request,
PodmanJsonContext.Default.NetworkDisconnectRequest,
cancellationToken: cancellationToken
);
}

View File

@ -1,3 +1,4 @@
using MaksIT.PodmanClientDotNet;
using MaksIT.PodmanClientDotNet.Dtos.Common;
using MaksIT.PodmanClientDotNet.Dtos.Pod;
using MaksIT.PodmanClientDotNet.Models.Pod;
@ -5,18 +6,19 @@ using MaksIT.Results;
public partial class PodmanClient {
public Task<Result<PodListEntryDto?>> CreatePodAsync(PodCreateRequest request, CancellationToken cancellationToken = default) =>
PostJsonAsync<PodCreateRequest, PodListEntryDto>("/libpod/pods/create", "Create pod", request, cancellationToken: cancellationToken);
PostJsonAsync<PodCreateRequest, PodListEntryDto>("/libpod/pods/create", "Create pod", request, PodmanJsonContext.Default.PodCreateRequest, PodmanJsonContext.Default.PodListEntryDto, cancellationToken: cancellationToken);
public Task<Result<List<PodListEntryDto>?>> ListPodsAsync(bool all = false, CancellationToken cancellationToken = default) =>
GetJsonAsync<List<PodListEntryDto>>(
"/libpod/pods/json",
"List pods",
PodmanJsonContext.Default.ListPodListEntryDto,
[("all", all.ToString().ToLowerInvariant())],
cancellationToken
);
public Task<Result<PodInspectDto?>> InspectPodAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<PodInspectDto>($"/libpod/pods/{Uri.EscapeDataString(name)}/json", "Inspect pod", cancellationToken: cancellationToken);
GetJsonAsync<PodInspectDto>($"/libpod/pods/{Uri.EscapeDataString(name)}/json", "Inspect pod", PodmanJsonContext.Default.PodInspectDto, cancellationToken: cancellationToken);
public Task<Result> PodExistsAsync(string name, CancellationToken cancellationToken = default) =>
GetWithoutBodyAsync($"/libpod/pods/{Uri.EscapeDataString(name)}/exists", "Pod exists", cancellationToken: cancellationToken);
@ -63,11 +65,11 @@ public partial class PodmanClient {
PostWithoutBodyAsync($"/libpod/pods/{Uri.EscapeDataString(name)}/unpause", "Unpause pod", cancellationToken: cancellationToken);
public Task<Result<PruneReportDto?>> PrunePodsAsync(CancellationToken cancellationToken = default) =>
PostLibpodAsync<PruneReportDto>("/libpod/pods/prune", "Prune pods", cancellationToken: cancellationToken);
PostLibpodAsync<PruneReportDto>("/libpod/pods/prune", "Prune pods", PodmanJsonContext.Default.PruneReportDto, cancellationToken: cancellationToken);
public Task<Result<PodTopDto?>> TopPodAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<PodTopDto>($"/libpod/pods/{Uri.EscapeDataString(name)}/top", "Top pod", cancellationToken: cancellationToken);
GetJsonAsync<PodTopDto>($"/libpod/pods/{Uri.EscapeDataString(name)}/top", "Top pod", PodmanJsonContext.Default.PodTopDto, cancellationToken: cancellationToken);
public Task<Result<PodStatsResponseDto?>> GetPodsStatsAsync(CancellationToken cancellationToken = default) =>
GetJsonAsync<PodStatsResponseDto>("/libpod/pods/stats", "Get pods stats", cancellationToken: cancellationToken);
GetJsonAsync<PodStatsResponseDto>("/libpod/pods/stats", "Get pods stats", PodmanJsonContext.Default.PodStatsResponseDto, cancellationToken: cancellationToken);
}

View File

@ -1,8 +1,9 @@
using MaksIT.PodmanClientDotNet;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using MaksIT.Core.Extensions;
using MaksIT.PodmanClientDotNet.Dtos.Build;
using MaksIT.PodmanClientDotNet.Dtos.Image;
using MaksIT.PodmanClientDotNet.Internal;
@ -65,7 +66,7 @@ public partial class PodmanClient {
Height = height,
Width = width,
};
var body = Encoding.UTF8.GetBytes(startExecRequest.ToJson());
var body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(startExecRequest, PodmanJsonContext.Default.StartExecRequest));
var query = BuildQuery([]);
var hijack = await PodmanHijackConnection.ConnectAsync(
@ -122,7 +123,7 @@ public partial class PodmanClient {
return streamResult.ToResultOfType<IPodmanProgressSession<PullImageResponseDto>>(null!);
return Result<IPodmanProgressSession<PullImageResponseDto>?>.Ok(
new PodmanProgressSession<PullImageResponseDto>(streamResult.Value!)
new PodmanProgressSession<PullImageResponseDto>(streamResult.Value!, PodmanJsonContext.Default.PullImageResponseDto)
);
}
@ -169,7 +170,7 @@ public partial class PodmanClient {
return streamResult.ToResultOfType<IPodmanProgressSession<BuildProgressLineDto>>(null!);
return Result<IPodmanProgressSession<BuildProgressLineDto>?>.Ok(
new PodmanProgressSession<BuildProgressLineDto>(streamResult.Value!)
new PodmanProgressSession<BuildProgressLineDto>(streamResult.Value!, PodmanJsonContext.Default.BuildProgressLineDto)
);
}

View File

@ -1,22 +1,23 @@
using MaksIT.PodmanClientDotNet;
using MaksIT.PodmanClientDotNet.Dtos.Common;
using MaksIT.PodmanClientDotNet.Dtos.System;
using MaksIT.Results;
public partial class PodmanClient {
public Task<Result<LibpodPingDto?>> PingAsync(CancellationToken cancellationToken = default) =>
GetJsonAsync<LibpodPingDto>("/libpod/_ping", "Ping", cancellationToken: cancellationToken);
GetJsonAsync<LibpodPingDto>("/libpod/_ping", "Ping", PodmanJsonContext.Default.LibpodPingDto, cancellationToken: cancellationToken);
public Task<Result<LibpodVersionDto?>> GetVersionAsync(CancellationToken cancellationToken = default) =>
GetJsonAsync<LibpodVersionDto>("/libpod/version", "Get version", cancellationToken: cancellationToken);
GetJsonAsync<LibpodVersionDto>("/libpod/version", "Get version", PodmanJsonContext.Default.LibpodVersionDto, cancellationToken: cancellationToken);
public Task<Result<InfoDto?>> GetInfoAsync(CancellationToken cancellationToken = default) =>
GetJsonAsync<InfoDto>("/libpod/info", "Get info", cancellationToken: cancellationToken);
GetJsonAsync<InfoDto>("/libpod/info", "Get info", PodmanJsonContext.Default.InfoDto, cancellationToken: cancellationToken);
public Task<Result<SystemDfDto?>> GetSystemDiskUsageAsync(CancellationToken cancellationToken = default) =>
GetJsonAsync<SystemDfDto>("/libpod/system/df", "Get system disk usage", cancellationToken: cancellationToken);
GetJsonAsync<SystemDfDto>("/libpod/system/df", "Get system disk usage", PodmanJsonContext.Default.SystemDfDto, cancellationToken: cancellationToken);
public Task<Result<PruneReportDto?>> PruneSystemAsync(CancellationToken cancellationToken = default) =>
PostLibpodAsync<PruneReportDto>("/libpod/system/prune", "Prune system", cancellationToken: cancellationToken);
PostLibpodAsync<PruneReportDto>("/libpod/system/prune", "Prune system", PodmanJsonContext.Default.PruneReportDto, cancellationToken: cancellationToken);
public Task<Result<Stream?>> GetEventsAsync(CancellationToken cancellationToken = default) =>
GetStreamAsync("/libpod/events", "Get events", cancellationToken: cancellationToken);

View File

@ -1,3 +1,4 @@
using MaksIT.PodmanClientDotNet;
using MaksIT.PodmanClientDotNet.Dtos.Common;
using MaksIT.PodmanClientDotNet.Dtos.Volume;
using MaksIT.PodmanClientDotNet.Models.Volume;
@ -12,14 +13,16 @@ public partial class PodmanClient {
"/libpod/volumes/create",
"Create volume",
request,
PodmanJsonContext.Default.CreateVolumeRequest,
PodmanJsonContext.Default.VolumeInspectResponseDto,
cancellationToken: cancellationToken
);
public Task<Result<List<VolumeListEntryDto>?>> ListVolumesAsync(CancellationToken cancellationToken = default) =>
GetJsonAsync<List<VolumeListEntryDto>>("/libpod/volumes/json", "List volumes", cancellationToken: cancellationToken);
GetJsonAsync<List<VolumeListEntryDto>>("/libpod/volumes/json", "List volumes", PodmanJsonContext.Default.ListVolumeListEntryDto, cancellationToken: cancellationToken);
public Task<Result<VolumeInspectResponseDto?>> InspectVolumeAsync(string name, CancellationToken cancellationToken = default) =>
GetJsonAsync<VolumeInspectResponseDto>($"/libpod/volumes/{Uri.EscapeDataString(name)}/json", "Inspect volume", cancellationToken: cancellationToken);
GetJsonAsync<VolumeInspectResponseDto>($"/libpod/volumes/{Uri.EscapeDataString(name)}/json", "Inspect volume", PodmanJsonContext.Default.VolumeInspectResponseDto, cancellationToken: cancellationToken);
public Task<Result> DeleteVolumeAsync(string name, bool force = false, CancellationToken cancellationToken = default) =>
DeleteWithoutBodyAsync(
@ -30,5 +33,5 @@ public partial class PodmanClient {
);
public Task<Result<PruneReportDto?>> PruneVolumesAsync(CancellationToken cancellationToken = default) =>
PostLibpodAsync<PruneReportDto>("/libpod/volumes/prune", "Prune volumes", cancellationToken: cancellationToken);
PostLibpodAsync<PruneReportDto>("/libpod/volumes/prune", "Prune volumes", PodmanJsonContext.Default.PruneReportDto, cancellationToken: cancellationToken);
}

View File

@ -39,6 +39,9 @@
<!-- Deterministic builds for reproducibility -->
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
<!-- AOT / trimming compatibility -->
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
@ -46,7 +49,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="MaksIT.Core" Version="1.6.8" />
<PackageReference Include="MaksIT.Results" Version="2.0.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.9" />

View File

@ -0,0 +1,116 @@
using System.Text.Json.Serialization;
using MaksIT.PodmanClientDotNet.Dtos.Build;
using MaksIT.PodmanClientDotNet.Dtos.Common;
using MaksIT.PodmanClientDotNet.Dtos.Container;
using MaksIT.PodmanClientDotNet.Dtos.Exec;
using MaksIT.PodmanClientDotNet.Dtos.Generate;
using MaksIT.PodmanClientDotNet.Dtos.Image;
using MaksIT.PodmanClientDotNet.Dtos.Manifest;
using MaksIT.PodmanClientDotNet.Dtos.Network;
using MaksIT.PodmanClientDotNet.Dtos.Pod;
using MaksIT.PodmanClientDotNet.Dtos.System;
using MaksIT.PodmanClientDotNet.Dtos.Volume;
using MaksIT.PodmanClientDotNet.Models.Container;
using MaksIT.PodmanClientDotNet.Models.Exec;
using MaksIT.PodmanClientDotNet.Models.Network;
using MaksIT.PodmanClientDotNet.Models.Pod;
using MaksIT.PodmanClientDotNet.Models.Volume;
namespace MaksIT.PodmanClientDotNet;
// ----- Response / DTO types -----
// Common
[JsonSerializable(typeof(ErrorResponseDto))]
[JsonSerializable(typeof(IdResponseDto))]
[JsonSerializable(typeof(PruneReportDto))]
[JsonSerializable(typeof(ReportDto))]
// Build
[JsonSerializable(typeof(BuildProgressLineDto))]
[JsonSerializable(typeof(BuildReportDto))]
// Container
[JsonSerializable(typeof(ContainerChangesDto))]
[JsonSerializable(typeof(ContainerCommitDto))]
[JsonSerializable(typeof(ContainerHealthCheckDto))]
[JsonSerializable(typeof(ContainerInspectDto))]
[JsonSerializable(typeof(ContainerListEntryDto))]
[JsonSerializable(typeof(List<ContainerListEntryDto>))]
[JsonSerializable(typeof(ContainerMountDto))]
[JsonSerializable(typeof(ContainerStatsDto))]
[JsonSerializable(typeof(Dictionary<string, ContainerStatsDto>))]
[JsonSerializable(typeof(ContainerTopDto))]
[JsonSerializable(typeof(ContainerWaitDto))]
[JsonSerializable(typeof(CreateContainerResponseDto))]
[JsonSerializable(typeof(DeleteContainerResponseDto))]
[JsonSerializable(typeof(DeleteContainerResponseDto[]))]
[JsonSerializable(typeof(MountedContainersResponseDto))]
// Exec
[JsonSerializable(typeof(CreateExecResponseDto))]
[JsonSerializable(typeof(InspectExecResponseDto))]
// Generate
[JsonSerializable(typeof(GenerateSystemdDto))]
[JsonSerializable(typeof(PlayKubeReportDto))]
// Image
[JsonSerializable(typeof(ImageChangesDto))]
[JsonSerializable(typeof(ImageDeleteDto))]
[JsonSerializable(typeof(ImageDeleteDto[]))]
[JsonSerializable(typeof(ImageHistoryEntryDto))]
[JsonSerializable(typeof(List<ImageHistoryEntryDto>))]
[JsonSerializable(typeof(ImageImportDto))]
[JsonSerializable(typeof(ImageInspectDto))]
[JsonSerializable(typeof(ImageListEntryDto))]
[JsonSerializable(typeof(List<ImageListEntryDto>))]
[JsonSerializable(typeof(ImageLoadDto))]
[JsonSerializable(typeof(ImageRemoveResponseDto))]
[JsonSerializable(typeof(ImageSearchResultDto))]
[JsonSerializable(typeof(List<ImageSearchResultDto>))]
[JsonSerializable(typeof(ImageTreeDto))]
[JsonSerializable(typeof(PullImageResponseDto))]
// Manifest
[JsonSerializable(typeof(ManifestCreateDto))]
[JsonSerializable(typeof(ManifestInspectDto))]
// Network
[JsonSerializable(typeof(NetworkInspectDto))]
[JsonSerializable(typeof(NetworkListEntryDto))]
[JsonSerializable(typeof(List<NetworkListEntryDto>))]
// Pod
[JsonSerializable(typeof(PodInspectDto))]
[JsonSerializable(typeof(PodListEntryDto))]
[JsonSerializable(typeof(List<PodListEntryDto>))]
[JsonSerializable(typeof(PodTopDto))]
[JsonSerializable(typeof(PodStatsResponseDto))]
// System
[JsonSerializable(typeof(InfoDto))]
[JsonSerializable(typeof(LibpodPingDto))]
[JsonSerializable(typeof(LibpodVersionDto))]
[JsonSerializable(typeof(SystemDfDto))]
// Volume
[JsonSerializable(typeof(VolumeInspectResponseDto))]
[JsonSerializable(typeof(VolumeListEntryDto))]
[JsonSerializable(typeof(List<VolumeListEntryDto>))]
// ----- Request / model types -----
[JsonSerializable(typeof(CreateContainerRequest))]
[JsonSerializable(typeof(CreateExecRequest))]
[JsonSerializable(typeof(StartExecRequest))]
[JsonSerializable(typeof(NetworkCreateRequest))]
[JsonSerializable(typeof(NetworkConnectRequest))]
[JsonSerializable(typeof(NetworkDisconnectRequest))]
[JsonSerializable(typeof(PodCreateRequest))]
[JsonSerializable(typeof(CreateVolumeRequest))]
[JsonSerializable(typeof(ManifestAddRequestDto))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified, PropertyNameCaseInsensitive = true)]
internal sealed partial class PodmanJsonContext : JsonSerializerContext {
}

View File

@ -1,15 +1,16 @@
using System.Text.Json;
using MaksIT.Core.Extensions;
using System.Text.Json.Serialization.Metadata;
namespace MaksIT.PodmanClientDotNet.Streaming;
internal sealed class PodmanProgressSession<T> : IPodmanProgressSession<T> {
private readonly Stream _stream;
private readonly bool _ownsStream;
private readonly JsonTypeInfo<T> _typeInfo;
internal PodmanProgressSession(Stream stream, bool ownsStream = true) {
internal PodmanProgressSession(Stream stream, JsonTypeInfo<T> typeInfo, bool ownsStream = true) {
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
_typeInfo = typeInfo ?? throw new ArgumentNullException(nameof(typeInfo));
_ownsStream = ownsStream;
}
@ -24,7 +25,7 @@ internal sealed class PodmanProgressSession<T> : IPodmanProgressSession<T> {
T? item;
try {
item = line.ToObject<T>();
item = JsonSerializer.Deserialize(line, _typeInfo);
}
catch (JsonException) {
continue;

View File

@ -10,7 +10,7 @@ public class PodmanProgressSessionTests {
public async Task ReadProgressAsync_ParsesNdjsonLines() {
var json = "{\"status\":\"Pulling fs layer\"}\n{\"id\":\"abc\"}\n";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
await using var session = new PodmanProgressSession<PullImageResponseDto>(stream, ownsStream: false);
await using var session = new PodmanProgressSession<PullImageResponseDto>(stream, PodmanJsonContext.Default.PullImageResponseDto, ownsStream: false);
var items = new List<PullImageResponseDto>();
await foreach (var item in session.ReadProgressAsync(TestContext.Current.CancellationToken))