up
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user