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
102 lines
3.9 KiB
C#
102 lines
3.9 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace StellaOps.Policy;
|
|
|
|
public sealed class PolicySnapshotStore
|
|
{
|
|
private readonly IPolicySnapshotRepository _snapshotRepository;
|
|
private readonly IPolicyAuditRepository _auditRepository;
|
|
private readonly TimeProvider _timeProvider;
|
|
private readonly ILogger<PolicySnapshotStore> _logger;
|
|
private readonly SemaphoreSlim _mutex = new(1, 1);
|
|
|
|
public PolicySnapshotStore(
|
|
IPolicySnapshotRepository snapshotRepository,
|
|
IPolicyAuditRepository auditRepository,
|
|
TimeProvider? timeProvider,
|
|
ILogger<PolicySnapshotStore> logger)
|
|
{
|
|
_snapshotRepository = snapshotRepository ?? throw new ArgumentNullException(nameof(snapshotRepository));
|
|
_auditRepository = auditRepository ?? throw new ArgumentNullException(nameof(auditRepository));
|
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
public async Task<PolicySnapshotSaveResult> SaveAsync(PolicySnapshotContent content, CancellationToken cancellationToken = default)
|
|
{
|
|
if (content is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(content));
|
|
}
|
|
|
|
var bindingResult = PolicyBinder.Bind(content.Content, content.Format);
|
|
if (!bindingResult.Success)
|
|
{
|
|
_logger.LogWarning("Policy snapshot rejected due to validation errors (Format: {Format})", content.Format);
|
|
return new PolicySnapshotSaveResult(false, false, string.Empty, null, bindingResult);
|
|
}
|
|
|
|
var digest = PolicyDigest.Compute(bindingResult.Document);
|
|
|
|
await _mutex.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
try
|
|
{
|
|
var latest = await _snapshotRepository.GetLatestAsync(cancellationToken).ConfigureAwait(false);
|
|
if (latest is not null && string.Equals(latest.Digest, digest, StringComparison.Ordinal))
|
|
{
|
|
_logger.LogInformation("Policy snapshot unchanged; digest {Digest} matches revision {RevisionId}", digest, latest.RevisionId);
|
|
return new PolicySnapshotSaveResult(true, false, digest, latest, bindingResult);
|
|
}
|
|
|
|
var revisionNumber = (latest?.RevisionNumber ?? 0) + 1;
|
|
var revisionId = $"rev-{revisionNumber}";
|
|
var createdAt = _timeProvider.GetUtcNow();
|
|
|
|
var scoringConfig = PolicyScoringConfig.Default;
|
|
|
|
var snapshot = new PolicySnapshot(
|
|
revisionNumber,
|
|
revisionId,
|
|
digest,
|
|
createdAt,
|
|
content.Actor,
|
|
content.Format,
|
|
bindingResult.Document,
|
|
bindingResult.Issues,
|
|
scoringConfig);
|
|
|
|
await _snapshotRepository.AddAsync(snapshot, cancellationToken).ConfigureAwait(false);
|
|
|
|
var auditMessage = content.Description ?? "Policy snapshot created";
|
|
var auditEntry = new PolicyAuditEntry(
|
|
Guid.NewGuid(),
|
|
createdAt,
|
|
"snapshot.created",
|
|
revisionId,
|
|
digest,
|
|
content.Actor,
|
|
auditMessage);
|
|
|
|
await _auditRepository.AddAsync(auditEntry, cancellationToken).ConfigureAwait(false);
|
|
|
|
_logger.LogInformation(
|
|
"Policy snapshot saved. Revision {RevisionId}, digest {Digest}, issues {IssueCount}",
|
|
revisionId,
|
|
digest,
|
|
bindingResult.Issues.Length);
|
|
|
|
return new PolicySnapshotSaveResult(true, true, digest, snapshot, bindingResult);
|
|
}
|
|
finally
|
|
{
|
|
_mutex.Release();
|
|
}
|
|
}
|
|
|
|
public Task<PolicySnapshot?> GetLatestAsync(CancellationToken cancellationToken = default)
|
|
=> _snapshotRepository.GetLatestAsync(cancellationToken);
|
|
}
|