audit, advisories and doctors/setup work

This commit is contained in:
master
2026-01-13 18:53:39 +02:00
parent 9ca7cb183e
commit d7be6ba34b
811 changed files with 54242 additions and 4056 deletions

View File

@@ -37,6 +37,7 @@ internal sealed class RuntimeEventsClient : IRuntimeEventsClient
private readonly IOptionsMonitor<ZastavaRuntimeOptions> runtimeOptions;
private readonly IOptionsMonitor<ZastavaObserverOptions> observerOptions;
private readonly IZastavaRuntimeMetrics runtimeMetrics;
private readonly TimeProvider timeProvider;
private readonly ILogger<RuntimeEventsClient> logger;
public RuntimeEventsClient(
@@ -45,6 +46,7 @@ internal sealed class RuntimeEventsClient : IRuntimeEventsClient
IOptionsMonitor<ZastavaRuntimeOptions> runtimeOptions,
IOptionsMonitor<ZastavaObserverOptions> observerOptions,
IZastavaRuntimeMetrics runtimeMetrics,
TimeProvider timeProvider,
ILogger<RuntimeEventsClient> logger)
{
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
@@ -52,6 +54,7 @@ internal sealed class RuntimeEventsClient : IRuntimeEventsClient
this.runtimeOptions = runtimeOptions ?? throw new ArgumentNullException(nameof(runtimeOptions));
this.observerOptions = observerOptions ?? throw new ArgumentNullException(nameof(observerOptions));
this.runtimeMetrics = runtimeMetrics ?? throw new ArgumentNullException(nameof(runtimeMetrics));
this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -109,7 +112,7 @@ internal sealed class RuntimeEventsClient : IRuntimeEventsClient
if (response.StatusCode == HttpStatusCode.TooManyRequests)
{
var retryAfter = ParseRetryAfter(response.Headers.RetryAfter) ?? TimeSpan.FromSeconds(5);
var retryAfter = ParseRetryAfter(response.Headers.RetryAfter, timeProvider) ?? TimeSpan.FromSeconds(5);
logger.LogWarning("Runtime events publish rate limited (batchId={BatchId}, retryAfter={RetryAfter}).", request.BatchId, retryAfter);
return RuntimeEventPublishResult.FromRateLimit(retryAfter);
}
@@ -159,8 +162,10 @@ internal sealed class RuntimeEventsClient : IRuntimeEventsClient
runtimeMetrics.BackendLatencyMs.Record(elapsedMs, tags);
}
private static TimeSpan? ParseRetryAfter(RetryConditionHeaderValue? retryAfter)
internal static TimeSpan? ParseRetryAfter(RetryConditionHeaderValue? retryAfter, TimeProvider timeProvider)
{
ArgumentNullException.ThrowIfNull(timeProvider);
if (retryAfter is null)
{
return null;
@@ -173,21 +178,21 @@ internal sealed class RuntimeEventsClient : IRuntimeEventsClient
if (retryAfter.Date.HasValue)
{
var delta = retryAfter.Date.Value.UtcDateTime - DateTime.UtcNow;
var delta = retryAfter.Date.Value - timeProvider.GetUtcNow();
return delta > TimeSpan.Zero ? delta : TimeSpan.Zero;
}
return null;
}
private static string Truncate(string? value, int maxLength = 512)
internal static string Truncate(string? value, int maxLength = 512)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
return value.Length <= maxLength ? value : value[..maxLength] + "";
return value.Length <= maxLength ? value : value[..maxLength] + "...";
}
}

View File

@@ -19,27 +19,37 @@ internal sealed class DockerWindowsRuntimeClient : IWindowsContainerRuntimeClien
PropertyNameCaseInsensitive = true
};
private const string DefaultPipeName = "docker_engine";
internal const string DefaultPipeName = "docker_engine";
internal static readonly TimeSpan DefaultConnectTimeout = TimeSpan.FromSeconds(5);
internal static readonly Uri DefaultBaseAddress = new("http://localhost/");
private readonly string _pipeName;
private readonly TimeSpan _connectTimeout;
private readonly HttpClient _httpClient;
private readonly ILogger<DockerWindowsRuntimeClient> _logger;
private bool _disposed;
public DockerWindowsRuntimeClient(
HttpClient httpClient,
ILogger<DockerWindowsRuntimeClient> logger,
string? pipeName = null,
TimeSpan? connectTimeout = null)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_pipeName = pipeName ?? DefaultPipeName;
_connectTimeout = connectTimeout ?? TimeSpan.FromSeconds(5);
_httpClient = CreateHttpClient(_pipeName, _connectTimeout);
_connectTimeout = connectTimeout ?? DefaultConnectTimeout;
}
private static HttpClient CreateHttpClient(string pipeName, TimeSpan connectTimeout)
internal static SocketsHttpHandler CreateNamedPipeHandler(string pipeName, TimeSpan connectTimeout)
{
if (string.IsNullOrWhiteSpace(pipeName))
{
throw new ArgumentException("Pipe name must be provided.", nameof(pipeName));
}
if (connectTimeout <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(connectTimeout), "Connect timeout must be positive.");
}
var handler = new SocketsHttpHandler
{
ConnectCallback = async (context, cancellationToken) =>
@@ -54,12 +64,7 @@ internal sealed class DockerWindowsRuntimeClient : IWindowsContainerRuntimeClien
},
ConnectTimeout = connectTimeout
};
return new HttpClient(handler)
{
BaseAddress = new Uri("http://localhost/"),
Timeout = Timeout.InfiniteTimeSpan
};
return handler;
}
public async Task<bool> IsAvailableAsync(CancellationToken cancellationToken)

View File

@@ -332,7 +332,7 @@ public sealed record ContainerEvent
/// <summary>
/// Event timestamp.
/// </summary>
public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.UtcNow;
public required DateTimeOffset Timestamp { get; init; }
}
/// <summary>

View File

@@ -0,0 +1,53 @@
using System;
using System.Net.Http.Headers;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Zastava.Observer.Backend;
using Xunit;
namespace StellaOps.Zastava.Observer.Tests.Backend;
public sealed class RuntimeEventsClientTests
{
[Fact]
public void ParseRetryAfter_WithDelta_ReturnsDelta()
{
var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero));
var delta = TimeSpan.FromSeconds(12);
var header = new RetryConditionHeaderValue(delta);
var result = RuntimeEventsClient.ParseRetryAfter(header, timeProvider);
Assert.Equal(delta, result);
}
[Fact]
public void ParseRetryAfter_WithDate_UsesTimeProvider()
{
var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero));
var header = new RetryConditionHeaderValue(timeProvider.GetUtcNow().AddSeconds(30));
var result = RuntimeEventsClient.ParseRetryAfter(header, timeProvider);
Assert.Equal(TimeSpan.FromSeconds(30), result);
}
[Fact]
public void ParseRetryAfter_WithPastDate_ReturnsZero()
{
var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 1, 0, 0, 30, TimeSpan.Zero));
var header = new RetryConditionHeaderValue(timeProvider.GetUtcNow().AddSeconds(-10));
var result = RuntimeEventsClient.ParseRetryAfter(header, timeProvider);
Assert.Equal(TimeSpan.Zero, result);
}
[Fact]
public void Truncate_UsesAsciiSuffix()
{
var value = "abcdefghijklmnopqrstuvwxyz";
var result = RuntimeEventsClient.Truncate(value, maxLength: 3);
Assert.Equal("abc...", result);
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
@@ -228,6 +229,18 @@ public sealed class WindowsContainerRuntimeIntegrationTests
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
Environment.GetEnvironmentVariable("ZASTAVA_WINDOWS_INTEGRATION_TESTS") == "true";
private static HttpClient CreateDockerHttpClient()
{
var handler = DockerWindowsRuntimeClient.CreateNamedPipeHandler(
DockerWindowsRuntimeClient.DefaultPipeName,
DockerWindowsRuntimeClient.DefaultConnectTimeout);
return new HttpClient(handler)
{
BaseAddress = DockerWindowsRuntimeClient.DefaultBaseAddress,
Timeout = Timeout.InfiniteTimeSpan
};
}
[Fact]
public async Task WindowsLibraryHashCollector_CollectCurrentProcess_ReturnsModules()
{
@@ -324,7 +337,9 @@ public sealed class WindowsContainerRuntimeIntegrationTests
return;
}
await using var client = new DockerWindowsRuntimeClient(NullLogger<DockerWindowsRuntimeClient>.Instance);
await using var client = new DockerWindowsRuntimeClient(
CreateDockerHttpClient(),
NullLogger<DockerWindowsRuntimeClient>.Instance);
var available = await client.IsAvailableAsync(CancellationToken.None);
@@ -340,7 +355,9 @@ public sealed class WindowsContainerRuntimeIntegrationTests
return;
}
await using var client = new DockerWindowsRuntimeClient(NullLogger<DockerWindowsRuntimeClient>.Instance);
await using var client = new DockerWindowsRuntimeClient(
CreateDockerHttpClient(),
NullLogger<DockerWindowsRuntimeClient>.Instance);
var identity = await client.GetIdentityAsync(CancellationToken.None);
@@ -359,7 +376,9 @@ public sealed class WindowsContainerRuntimeIntegrationTests
return;
}
await using var client = new DockerWindowsRuntimeClient(NullLogger<DockerWindowsRuntimeClient>.Instance);
await using var client = new DockerWindowsRuntimeClient(
CreateDockerHttpClient(),
NullLogger<DockerWindowsRuntimeClient>.Instance);
var containers = await client.ListContainersAsync(
WindowsContainerState.Running,