This commit is contained in:
StellaOps Bot
2025-12-13 02:22:15 +02:00
parent 564df71bfb
commit 999e26a48e
395 changed files with 25045 additions and 2224 deletions

View File

@@ -13,7 +13,7 @@ public static class ScannerWebServiceOptionsValidator
{
private static readonly HashSet<string> SupportedStorageDrivers = new(StringComparer.OrdinalIgnoreCase)
{
"mongo"
"postgres"
};
private static readonly HashSet<string> SupportedQueueDrivers = new(StringComparer.OrdinalIgnoreCase)
@@ -101,7 +101,7 @@ public static class ScannerWebServiceOptionsValidator
{
if (!SupportedStorageDrivers.Contains(storage.Driver))
{
throw new InvalidOperationException($"Unsupported storage driver '{storage.Driver}'. Supported drivers: mongo.");
throw new InvalidOperationException($"Unsupported storage driver '{storage.Driver}'. Supported drivers: postgres.");
}
if (string.IsNullOrWhiteSpace(storage.Dsn))

View File

@@ -309,6 +309,41 @@ else
});
}
// Concelier Linkset integration for advisory enrichment
builder.Services.Configure<ConcelierLinksetOptions>(builder.Configuration.GetSection(ConcelierLinksetOptions.SectionName));
builder.Services.AddHttpClient<ConcelierHttpLinksetQueryService>((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<ConcelierLinksetOptions>>().Value;
if (!string.IsNullOrWhiteSpace(options.BaseUrl))
{
client.BaseAddress = new Uri(options.BaseUrl);
}
client.Timeout = TimeSpan.FromSeconds(Math.Max(1, options.TimeoutSeconds));
if (!string.IsNullOrWhiteSpace(options.ApiKey))
{
var header = string.IsNullOrWhiteSpace(options.ApiKeyHeader) ? "Authorization" : options.ApiKeyHeader;
client.DefaultRequestHeaders.TryAddWithoutValidation(header, options.ApiKey);
}
})
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
AutomaticDecompression = System.Net.DecompressionMethods.All
});
builder.Services.AddSingleton<IAdvisoryLinksetQueryService>(sp =>
{
var options = sp.GetRequiredService<IOptions<ConcelierLinksetOptions>>().Value;
if (options.Enabled && !string.IsNullOrWhiteSpace(options.BaseUrl))
{
return sp.GetRequiredService<ConcelierHttpLinksetQueryService>();
}
return new NullAdvisoryLinksetQueryService();
});
var app = builder.Build();
// Fail fast if surface configuration is invalid at startup.
@@ -423,36 +458,3 @@ internal sealed class SurfaceCacheOptionsConfigurator : IConfigureOptions<Surfac
options.RootDirectory = settings.CacheRoot.FullName;
}
}
builder.Services.Configure<ConcelierLinksetOptions>(builder.Configuration.GetSection(ConcelierLinksetOptions.SectionName));
builder.Services.AddHttpClient<ConcelierHttpLinksetQueryService>((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<ConcelierLinksetOptions>>().Value;
if (!string.IsNullOrWhiteSpace(options.BaseUrl))
{
client.BaseAddress = new Uri(options.BaseUrl);
}
client.Timeout = TimeSpan.FromSeconds(Math.Max(1, options.TimeoutSeconds));
if (!string.IsNullOrWhiteSpace(options.ApiKey))
{
var header = string.IsNullOrWhiteSpace(options.ApiKeyHeader) ? "Authorization" : options.ApiKeyHeader;
client.DefaultRequestHeaders.TryAddWithoutValidation(header, options.ApiKey);
}
})
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
AutomaticDecompression = System.Net.DecompressionMethods.All
});
builder.Services.AddSingleton<IAdvisoryLinksetQueryService>(sp =>
{
var options = sp.GetRequiredService<IOptions<ConcelierLinksetOptions>>().Value;
if (options.Enabled && !string.IsNullOrWhiteSpace(options.BaseUrl))
{
return sp.GetRequiredService<ConcelierHttpLinksetQueryService>();
}
return new NullAdvisoryLinksetQueryService();
});

View File

@@ -0,0 +1,80 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Messaging;
using StellaOps.Messaging.Abstractions;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Options;
namespace StellaOps.Scanner.WebService.Services;
/// <summary>
/// Transport-agnostic implementation of <see cref="IPlatformEventPublisher"/> using StellaOps.Messaging abstractions.
/// Works with any configured transport (Valkey, PostgreSQL, InMemory).
/// </summary>
internal sealed class MessagingPlatformEventPublisher : IPlatformEventPublisher
{
private readonly IEventStream<OrchestratorEvent> _eventStream;
private readonly ILogger<MessagingPlatformEventPublisher> _logger;
private readonly TimeSpan _publishTimeout;
private readonly long? _maxStreamLength;
public MessagingPlatformEventPublisher(
IEventStreamFactory eventStreamFactory,
IOptions<ScannerWebServiceOptions> options,
ILogger<MessagingPlatformEventPublisher> logger)
{
ArgumentNullException.ThrowIfNull(eventStreamFactory);
ArgumentNullException.ThrowIfNull(options);
var eventsOptions = options.Value.Events ?? throw new InvalidOperationException("Events options are required when messaging publisher is registered.");
if (!eventsOptions.Enabled)
{
throw new InvalidOperationException("MessagingPlatformEventPublisher requires events emission to be enabled.");
}
var streamName = string.IsNullOrWhiteSpace(eventsOptions.Stream) ? "stella.events" : eventsOptions.Stream;
_maxStreamLength = eventsOptions.MaxStreamLength > 0 ? eventsOptions.MaxStreamLength : null;
_publishTimeout = TimeSpan.FromSeconds(eventsOptions.PublishTimeoutSeconds <= 0 ? 5 : eventsOptions.PublishTimeoutSeconds);
_eventStream = eventStreamFactory.Create<OrchestratorEvent>(new EventStreamOptions
{
StreamName = streamName,
MaxLength = _maxStreamLength,
ApproximateTrimming = true,
});
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_logger.LogInformation("Initialized messaging platform event publisher for stream {Stream}.", streamName);
}
public async Task PublishAsync(OrchestratorEvent @event, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(@event);
cancellationToken.ThrowIfCancellationRequested();
var publishOptions = new EventPublishOptions
{
IdempotencyKey = @event.IdempotencyKey,
TenantId = @event.Tenant,
CorrelationId = @event.CorrelationId,
MaxStreamLength = _maxStreamLength,
Headers = new Dictionary<string, string>
{
["kind"] = @event.Kind,
["occurredAt"] = @event.OccurredAt.ToString("O")
}
};
var publishTask = _eventStream.PublishAsync(@event, publishOptions, cancellationToken);
if (_publishTimeout > TimeSpan.Zero)
{
await publishTask.AsTask().WaitAsync(_publishTimeout, cancellationToken).ConfigureAwait(false);
}
else
{
await publishTask.ConfigureAwait(false);
}
}
}

View File

@@ -13,7 +13,7 @@
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="YamlDotNet" Version="13.7.1" />
<PackageReference Include="StackExchange.Redis" Version="2.8.24" />
<PackageReference Include="StackExchange.Redis" Version="2.8.37" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Configuration/StellaOps.Configuration.csproj" />
@@ -38,5 +38,6 @@
<ProjectReference Include="../../Zastava/__Libraries/StellaOps.Zastava.Core/StellaOps.Zastava.Core.csproj" />
<ProjectReference Include="../../Concelier/__Libraries/StellaOps.Concelier.Core/StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="../../Concelier/__Libraries/StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Messaging/StellaOps.Messaging.csproj" />
</ItemGroup>
</Project>