81 lines
3.0 KiB
C#
81 lines
3.0 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using StellaOps.Scanner.WebService.Domain;
|
|
using StellaOps.Scanner.WebService.Utilities;
|
|
|
|
namespace StellaOps.Scanner.WebService.Services;
|
|
|
|
public sealed class InMemoryScanCoordinator : IScanCoordinator
|
|
{
|
|
private sealed record ScanEntry(ScanSnapshot Snapshot);
|
|
|
|
private readonly ConcurrentDictionary<string, ScanEntry> scans = new(StringComparer.OrdinalIgnoreCase);
|
|
private readonly TimeProvider timeProvider;
|
|
private readonly IScanProgressPublisher progressPublisher;
|
|
|
|
public InMemoryScanCoordinator(TimeProvider timeProvider, IScanProgressPublisher progressPublisher)
|
|
{
|
|
this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
|
this.progressPublisher = progressPublisher ?? throw new ArgumentNullException(nameof(progressPublisher));
|
|
}
|
|
|
|
public ValueTask<ScanSubmissionResult> SubmitAsync(ScanSubmission submission, CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(submission);
|
|
|
|
var normalizedTarget = submission.Target.Normalize();
|
|
var metadata = submission.Metadata ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
var scanId = ScanIdGenerator.Create(normalizedTarget, submission.Force, submission.ClientRequestId, metadata);
|
|
var now = timeProvider.GetUtcNow();
|
|
|
|
var eventData = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["force"] = submission.Force,
|
|
};
|
|
foreach (var pair in metadata)
|
|
{
|
|
eventData[$"meta.{pair.Key}"] = pair.Value;
|
|
}
|
|
|
|
ScanEntry entry = scans.AddOrUpdate(
|
|
scanId.Value,
|
|
_ => new ScanEntry(new ScanSnapshot(
|
|
scanId,
|
|
normalizedTarget,
|
|
ScanStatus.Pending,
|
|
now,
|
|
now,
|
|
null)),
|
|
(_, existing) =>
|
|
{
|
|
if (submission.Force)
|
|
{
|
|
var snapshot = existing.Snapshot with
|
|
{
|
|
Status = ScanStatus.Pending,
|
|
UpdatedAt = now,
|
|
FailureReason = null
|
|
};
|
|
return new ScanEntry(snapshot);
|
|
}
|
|
|
|
return existing;
|
|
});
|
|
|
|
var created = entry.Snapshot.CreatedAt == now;
|
|
var state = entry.Snapshot.Status.ToString();
|
|
progressPublisher.Publish(scanId, state, created ? "queued" : "requeued", eventData);
|
|
return ValueTask.FromResult(new ScanSubmissionResult(entry.Snapshot, created));
|
|
}
|
|
|
|
public ValueTask<ScanSnapshot?> GetAsync(ScanId scanId, CancellationToken cancellationToken)
|
|
{
|
|
if (scans.TryGetValue(scanId.Value, out var entry))
|
|
{
|
|
return ValueTask.FromResult<ScanSnapshot?>(entry.Snapshot);
|
|
}
|
|
|
|
return ValueTask.FromResult<ScanSnapshot?>(null);
|
|
}
|
|
}
|