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

@@ -0,0 +1,106 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Messaging;
using StellaOps.Messaging.Abstractions;
using StellaOps.Scheduler.WebService.Options;
namespace StellaOps.Scheduler.WebService.GraphJobs.Events;
/// <summary>
/// Transport-agnostic implementation of <see cref="IGraphJobCompletionPublisher"/> using StellaOps.Messaging abstractions.
/// Works with any configured transport (Valkey, PostgreSQL, InMemory).
/// </summary>
internal sealed class MessagingGraphJobEventPublisher : IGraphJobCompletionPublisher
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
private readonly IOptionsMonitor<SchedulerEventsOptions> _options;
private readonly IEventStream<GraphJobCompletedEvent> _eventStream;
private readonly ILogger<MessagingGraphJobEventPublisher> _logger;
public MessagingGraphJobEventPublisher(
IOptionsMonitor<SchedulerEventsOptions> options,
IEventStreamFactory eventStreamFactory,
ILogger<MessagingGraphJobEventPublisher> logger)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(eventStreamFactory);
_options = options;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
var eventsOptions = options.CurrentValue?.GraphJobs ?? new GraphJobEventsOptions();
var streamKey = string.IsNullOrWhiteSpace(eventsOptions.Stream) ? "stella.events" : eventsOptions.Stream;
var maxStreamLength = eventsOptions.MaxStreamLength > 0 ? eventsOptions.MaxStreamLength : (long?)null;
_eventStream = eventStreamFactory.Create<GraphJobCompletedEvent>(new EventStreamOptions
{
StreamName = streamKey,
MaxLength = maxStreamLength,
ApproximateTrimming = true,
});
_logger.LogInformation("Initialized messaging graph job event publisher for stream {Stream}.", streamKey);
}
public async Task PublishAsync(GraphJobCompletionNotification notification, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(notification);
var options = _options.CurrentValue?.GraphJobs ?? new GraphJobEventsOptions();
if (!options.Enabled)
{
_logger.LogDebug("Graph job events disabled; skipping emission for {JobId}.", notification.Job.Id);
return;
}
try
{
var envelope = GraphJobEventFactory.Create(notification);
var publishOptions = new EventPublishOptions
{
TenantId = envelope.Tenant,
MaxStreamLength = options.MaxStreamLength > 0 ? options.MaxStreamLength : null,
Headers = new Dictionary<string, string>
{
["kind"] = envelope.Kind,
["occurredAt"] = envelope.Timestamp.ToString("O"),
["jobId"] = notification.Job.Id,
["status"] = notification.Status.ToString()
}
};
var publishTask = _eventStream.PublishAsync(envelope, publishOptions, cancellationToken);
if (options.PublishTimeoutSeconds > 0)
{
var timeout = TimeSpan.FromSeconds(options.PublishTimeoutSeconds);
await publishTask.AsTask().WaitAsync(timeout, cancellationToken).ConfigureAwait(false);
}
else
{
await publishTask.ConfigureAwait(false);
}
_logger.LogDebug("Published graph job event {JobId} to stream.", notification.Job.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to publish graph job completion for {JobId}; logging payload instead.", notification.Job.Id);
LogEnvelope(notification);
}
}
private void LogEnvelope(GraphJobCompletionNotification notification)
{
var envelope = GraphJobEventFactory.Create(notification);
var json = JsonSerializer.Serialize(envelope, SerializerOptions);
_logger.LogInformation("{EventJson}", json);
}
}

View File

@@ -13,8 +13,9 @@
<ProjectReference Include="../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Messaging/StellaOps.Messaging.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StackExchange.Redis" Version="2.8.24" />
<PackageReference Include="StackExchange.Redis" Version="2.8.37" />
</ItemGroup>
</Project>

View File

@@ -5,7 +5,7 @@ namespace StellaOps.Scheduler.WebService.VulnerabilityResolverJobs;
/// <summary>
/// Lightweight in-memory resolver job service to satisfy API contract and rate-limit callers.
/// Suitable for stub/air-gap scenarios; replace with Mongo-backed implementation when ready.
/// Suitable for stub/air-gap scenarios; replace with PostgreSQL-backed implementation when ready.
/// </summary>
public sealed class InMemoryResolverJobService : IResolverJobService
{

View File

@@ -102,7 +102,7 @@ internal sealed class BackfillRunner
{
Console.WriteLine($"Postgres graph job backfill starting (dry-run={_options.DryRun})");
// Placeholder: actual copy logic would map legacy Mongo export to new Postgres graph_jobs rows.
// Placeholder: actual copy logic would map legacy export to new Postgres graph_jobs rows.
if (_options.DryRun)
{
Console.WriteLine("Dry run: no changes applied.");

View File

@@ -4,7 +4,7 @@ using System.Text.Json.Serialization;
namespace StellaOps.Scheduler.Models;
/// <summary>
/// Scheduler configuration entity persisted in Mongo.
/// Scheduler configuration entity persisted in storage.
/// </summary>
public sealed record Schedule
{

View File

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StackExchange.Redis" Version="2.8.24" />
<PackageReference Include="StackExchange.Redis" Version="2.8.37" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />