Files
git.stella-ops.org/src/Policy/StellaOps.Policy.Engine/Services/InMemoryPolicyPackRepository.cs
2026-02-01 21:37:40 +02:00

128 lines
5.4 KiB
C#

using StellaOps.Policy.Engine.Domain;
using System.Collections.Concurrent;
namespace StellaOps.Policy.Engine.Services;
internal sealed class InMemoryPolicyPackRepository : IPolicyPackRepository
{
private readonly ConcurrentDictionary<string, PolicyPackRecord> packs = new(StringComparer.OrdinalIgnoreCase);
private readonly TimeProvider _timeProvider;
public InMemoryPolicyPackRepository(TimeProvider timeProvider)
{
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
}
public Task<PolicyPackRecord> CreateAsync(string packId, string? displayName, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(packId);
var created = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, displayName, _timeProvider.GetUtcNow()));
return Task.FromResult(created);
}
public Task<IReadOnlyList<PolicyPackRecord>> ListAsync(CancellationToken cancellationToken)
{
IReadOnlyList<PolicyPackRecord> list = packs.Values
.OrderBy(pack => pack.PackId, StringComparer.Ordinal)
.ToList();
return Task.FromResult(list);
}
public Task<PolicyRevisionRecord> UpsertRevisionAsync(string packId, int version, bool requiresTwoPersonApproval, PolicyRevisionStatus initialStatus, CancellationToken cancellationToken)
{
var pack = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, null, _timeProvider.GetUtcNow()));
int revisionVersion = version > 0 ? version : pack.GetNextVersion();
var revision = pack.GetOrAddRevision(
revisionVersion,
v => new PolicyRevisionRecord(v, requiresTwoPersonApproval, initialStatus, _timeProvider.GetUtcNow()));
if (revision.Status != initialStatus)
{
revision.SetStatus(initialStatus, _timeProvider.GetUtcNow());
}
return Task.FromResult(revision);
}
public Task<PolicyRevisionRecord?> GetRevisionAsync(string packId, int version, CancellationToken cancellationToken)
{
if (!packs.TryGetValue(packId, out var pack))
{
return Task.FromResult<PolicyRevisionRecord?>(null);
}
return Task.FromResult(pack.TryGetRevision(version, out var revision) ? revision : null);
}
public Task<PolicyActivationResult> RecordActivationAsync(string packId, int version, string actorId, DateTimeOffset timestamp, string? comment, CancellationToken cancellationToken)
{
if (!packs.TryGetValue(packId, out var pack))
{
return Task.FromResult(new PolicyActivationResult(PolicyActivationResultStatus.PackNotFound, null));
}
if (!pack.TryGetRevision(version, out var revision))
{
return Task.FromResult(new PolicyActivationResult(PolicyActivationResultStatus.RevisionNotFound, null));
}
if (revision.Status == PolicyRevisionStatus.Active)
{
return Task.FromResult(new PolicyActivationResult(PolicyActivationResultStatus.AlreadyActive, revision));
}
if (revision.Status != PolicyRevisionStatus.Approved)
{
return Task.FromResult(new PolicyActivationResult(PolicyActivationResultStatus.NotApproved, revision));
}
var approvalStatus = revision.AddApproval(new PolicyActivationApproval(actorId, timestamp, comment));
return Task.FromResult(approvalStatus switch
{
PolicyActivationApprovalStatus.Duplicate => new PolicyActivationResult(PolicyActivationResultStatus.DuplicateApproval, revision),
PolicyActivationApprovalStatus.Pending when revision.RequiresTwoPersonApproval
=> new PolicyActivationResult(PolicyActivationResultStatus.PendingSecondApproval, revision),
PolicyActivationApprovalStatus.Pending =>
ActivateRevision(revision, timestamp),
PolicyActivationApprovalStatus.ThresholdReached =>
ActivateRevision(revision, timestamp),
_ => throw new InvalidOperationException("Unknown activation approval status.")
});
}
private static PolicyActivationResult ActivateRevision(PolicyRevisionRecord revision, DateTimeOffset timestamp)
{
revision.SetStatus(PolicyRevisionStatus.Active, timestamp);
return new PolicyActivationResult(PolicyActivationResultStatus.Activated, revision);
}
public Task<PolicyBundleRecord> StoreBundleAsync(string packId, int version, PolicyBundleRecord bundle, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(bundle);
var pack = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, null, _timeProvider.GetUtcNow()));
var revision = pack.GetOrAddRevision(version > 0 ? version : pack.GetNextVersion(),
v => new PolicyRevisionRecord(v, requiresTwoPerson: false, status: PolicyRevisionStatus.Draft, _timeProvider.GetUtcNow()));
revision.SetBundle(bundle);
return Task.FromResult(bundle);
}
public Task<PolicyBundleRecord?> GetBundleAsync(string packId, int version, CancellationToken cancellationToken)
{
if (!packs.TryGetValue(packId, out var pack))
{
return Task.FromResult<PolicyBundleRecord?>(null);
}
if (!pack.TryGetRevision(version, out var revision))
{
return Task.FromResult<PolicyBundleRecord?>(null);
}
return Task.FromResult(revision.Bundle);
}
}