up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 00:20:26 +02:00
parent e1f1bef4c1
commit 564df71bfb
2376 changed files with 334389 additions and 328032 deletions

View File

@@ -1,29 +1,29 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Options;
using StellaOps.Scanner.WebService.Serialization;
namespace StellaOps.Scanner.WebService.Services;
internal sealed class RedisPlatformEventPublisher : IPlatformEventPublisher, IAsyncDisposable
{
private readonly ScannerWebServiceOptions.EventsOptions _options;
private readonly ILogger<RedisPlatformEventPublisher> _logger;
namespace StellaOps.Scanner.WebService.Services;
internal sealed class RedisPlatformEventPublisher : IPlatformEventPublisher, IAsyncDisposable
{
private readonly ScannerWebServiceOptions.EventsOptions _options;
private readonly ILogger<RedisPlatformEventPublisher> _logger;
private readonly IRedisConnectionFactory _connectionFactory;
private readonly TimeSpan _publishTimeout;
private readonly string _streamKey;
private readonly long? _maxStreamLength;
private readonly SemaphoreSlim _connectionGate = new(1, 1);
private IConnectionMultiplexer? _connection;
private bool _disposed;
public RedisPlatformEventPublisher(
private readonly string _streamKey;
private readonly long? _maxStreamLength;
private readonly SemaphoreSlim _connectionGate = new(1, 1);
private IConnectionMultiplexer? _connection;
private bool _disposed;
public RedisPlatformEventPublisher(
IOptions<ScannerWebServiceOptions> options,
IRedisConnectionFactory connectionFactory,
ILogger<RedisPlatformEventPublisher> logger)
@@ -32,23 +32,23 @@ internal sealed class RedisPlatformEventPublisher : IPlatformEventPublisher, IAs
ArgumentNullException.ThrowIfNull(connectionFactory);
_options = options.Value.Events ?? throw new InvalidOperationException("Events options are required when redis publisher is registered.");
if (!_options.Enabled)
{
throw new InvalidOperationException("RedisPlatformEventPublisher requires events emission to be enabled.");
}
if (!string.Equals(_options.Driver, "redis", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"RedisPlatformEventPublisher cannot be used with driver '{_options.Driver}'.");
}
if (!_options.Enabled)
{
throw new InvalidOperationException("RedisPlatformEventPublisher requires events emission to be enabled.");
}
if (!string.Equals(_options.Driver, "redis", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"RedisPlatformEventPublisher cannot be used with driver '{_options.Driver}'.");
}
_connectionFactory = connectionFactory;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_streamKey = string.IsNullOrWhiteSpace(_options.Stream) ? "stella.events" : _options.Stream;
_publishTimeout = TimeSpan.FromSeconds(_options.PublishTimeoutSeconds <= 0 ? 5 : _options.PublishTimeoutSeconds);
_maxStreamLength = _options.MaxStreamLength > 0 ? _options.MaxStreamLength : null;
}
}
public async Task PublishAsync(OrchestratorEvent @event, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(@event);
@@ -65,90 +65,90 @@ internal sealed class RedisPlatformEventPublisher : IPlatformEventPublisher, IAs
new("occurredAt", @event.OccurredAt.ToString("O")),
new("idempotencyKey", @event.IdempotencyKey)
};
int? maxLength = null;
if (_maxStreamLength.HasValue)
{
var clamped = Math.Min(_maxStreamLength.Value, int.MaxValue);
maxLength = (int)clamped;
}
var publishTask = maxLength.HasValue
? database.StreamAddAsync(_streamKey, entries, maxLength: maxLength, useApproximateMaxLength: true)
: database.StreamAddAsync(_streamKey, entries);
if (_publishTimeout > TimeSpan.Zero)
{
await publishTask.WaitAsync(_publishTimeout, cancellationToken).ConfigureAwait(false);
}
else
{
await publishTask.ConfigureAwait(false);
}
}
private async Task<IDatabase> GetDatabaseAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (_connection is not null && _connection.IsConnected)
{
return _connection.GetDatabase();
}
await _connectionGate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (_connection is null || !_connection.IsConnected)
{
var config = ConfigurationOptions.Parse(_options.Dsn);
config.AbortOnConnectFail = false;
if (_options.DriverSettings.TryGetValue("clientName", out var clientName) && !string.IsNullOrWhiteSpace(clientName))
{
config.ClientName = clientName;
}
if (_options.DriverSettings.TryGetValue("ssl", out var sslValue) && bool.TryParse(sslValue, out var ssl))
{
config.Ssl = ssl;
}
int? maxLength = null;
if (_maxStreamLength.HasValue)
{
var clamped = Math.Min(_maxStreamLength.Value, int.MaxValue);
maxLength = (int)clamped;
}
var publishTask = maxLength.HasValue
? database.StreamAddAsync(_streamKey, entries, maxLength: maxLength, useApproximateMaxLength: true)
: database.StreamAddAsync(_streamKey, entries);
if (_publishTimeout > TimeSpan.Zero)
{
await publishTask.WaitAsync(_publishTimeout, cancellationToken).ConfigureAwait(false);
}
else
{
await publishTask.ConfigureAwait(false);
}
}
private async Task<IDatabase> GetDatabaseAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (_connection is not null && _connection.IsConnected)
{
return _connection.GetDatabase();
}
await _connectionGate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (_connection is null || !_connection.IsConnected)
{
var config = ConfigurationOptions.Parse(_options.Dsn);
config.AbortOnConnectFail = false;
if (_options.DriverSettings.TryGetValue("clientName", out var clientName) && !string.IsNullOrWhiteSpace(clientName))
{
config.ClientName = clientName;
}
if (_options.DriverSettings.TryGetValue("ssl", out var sslValue) && bool.TryParse(sslValue, out var ssl))
{
config.Ssl = ssl;
}
_connection = await _connectionFactory.ConnectAsync(config, cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Connected Redis platform event publisher to stream {Stream}.", _streamKey);
}
}
finally
{
_connectionGate.Release();
}
return _connection!.GetDatabase();
}
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}
_disposed = true;
if (_connection is not null)
{
try
{
await _connection.CloseAsync();
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Error while closing Redis platform event publisher connection.");
}
_connection.Dispose();
}
_connectionGate.Dispose();
}
}
{
_connectionGate.Release();
}
return _connection!.GetDatabase();
}
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}
_disposed = true;
if (_connection is not null)
{
try
{
await _connection.CloseAsync();
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Error while closing Redis platform event publisher connection.");
}
_connection.Dispose();
}
_connectionGate.Dispose();
}
}