Files
git.stella-ops.org/src/Concelier/StellaOps.Concelier.WebService/Services/IncidentFileStore.cs
StellaOps Bot 6bee1fdcf5
Some checks failed
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
Docs CI / lint-and-preview (push) Has been cancelled
work
2025-11-25 08:01:23 +02:00

105 lines
3.7 KiB
C#

using System.Text.Json;
namespace StellaOps.Concelier.WebService.Services;
internal static class IncidentFileStore
{
private static readonly JsonSerializerOptions SerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false,
};
public static string GetIncidentFilePath(ConcelierOptions.EvidenceBundleOptions evidenceOptions, string tenant, string advisoryKey)
{
ArgumentNullException.ThrowIfNull(evidenceOptions);
ArgumentNullException.ThrowIfNull(tenant);
ArgumentNullException.ThrowIfNull(advisoryKey);
var root = evidenceOptions.RootAbsolute ?? evidenceOptions.Root ?? string.Empty;
return Path.Combine(root, tenant.Trim(), advisoryKey.Trim(), "incident.json");
}
public static async Task WriteAsync(
ConcelierOptions.EvidenceBundleOptions evidenceOptions,
string tenant,
string advisoryKey,
string reason,
int cooldownMinutes,
string? pipelineVersion,
DateTimeOffset now,
CancellationToken cancellationToken)
{
var path = GetIncidentFilePath(evidenceOptions, tenant, advisoryKey);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
var activatedAt = now.ToUniversalTime();
var cooldownUntil = activatedAt.AddMinutes(cooldownMinutes);
var payload = new IncidentFile
{
AdvisoryKey = advisoryKey.Trim(),
Tenant = tenant.Trim(),
Reason = string.IsNullOrWhiteSpace(reason) ? "unspecified" : reason.Trim(),
ActivatedAt = activatedAt,
CooldownUntil = cooldownUntil,
PipelineVersion = pipelineVersion,
};
var json = JsonSerializer.Serialize(payload, SerializerOptions);
await File.WriteAllTextAsync(path, json, cancellationToken).ConfigureAwait(false);
}
public static async Task<IncidentStatusResponse?> ReadAsync(
ConcelierOptions.EvidenceBundleOptions evidenceOptions,
string tenant,
string advisoryKey,
DateTimeOffset now,
CancellationToken cancellationToken)
{
var path = GetIncidentFilePath(evidenceOptions, tenant, advisoryKey);
if (!File.Exists(path))
{
return null;
}
await using var stream = File.OpenRead(path);
var payload = await JsonSerializer.DeserializeAsync<IncidentFile>(stream, SerializerOptions, cancellationToken).ConfigureAwait(false);
if (payload is null)
{
return null;
}
var active = payload.CooldownUntil > now.ToUniversalTime();
return new IncidentStatusResponse(
payload.AdvisoryKey,
payload.Tenant,
payload.Reason,
payload.ActivatedAt.ToUniversalTime().ToString("O"),
payload.CooldownUntil.ToUniversalTime().ToString("O"),
payload.PipelineVersion,
active);
}
public static Task DeleteAsync(ConcelierOptions.EvidenceBundleOptions evidenceOptions, string tenant, string advisoryKey, CancellationToken cancellationToken)
{
var path = GetIncidentFilePath(evidenceOptions, tenant, advisoryKey);
if (File.Exists(path))
{
File.Delete(path);
}
return Task.CompletedTask;
}
private sealed record IncidentFile
{
public string AdvisoryKey { get; init; } = string.Empty;
public string Tenant { get; init; } = string.Empty;
public string Reason { get; init; } = "unspecified";
public DateTimeOffset ActivatedAt { get; init; }
public DateTimeOffset CooldownUntil { get; init; }
public string? PipelineVersion { get; init; }
}
}