Files
git.stella-ops.org/src/ExportCenter/StellaOps.ExportCenter.RiskBundles/RiskBundleJob.cs
2026-02-01 21:37:40 +02:00

132 lines
5.4 KiB
C#

using Microsoft.Extensions.Logging;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace StellaOps.ExportCenter.RiskBundles;
public sealed record RiskBundleJobRequest(
RiskBundleBuildRequest BuildRequest,
string StoragePrefix = "risk-bundles",
string BundleFileName = "risk-bundle.tar.gz",
bool IncludeOsv = false,
bool AllowStaleOptional = true);
public sealed record RiskBundleJobOutcome(
RiskBundleManifest Manifest,
RiskBundleStorageMetadata ManifestStorage,
RiskBundleStorageMetadata ManifestSignatureStorage,
RiskBundleStorageMetadata BundleStorage,
RiskBundleStorageMetadata BundleSignatureStorage,
string ManifestJson,
string ManifestSignatureJson,
string BundleSignature,
string RootHash);
public sealed class RiskBundleJob
{
private readonly RiskBundleBuilder _builder;
private readonly IRiskBundleManifestSigner _signer;
private readonly IRiskBundleArchiveSigner _archiveSigner;
private readonly IRiskBundleObjectStore _objectStore;
private readonly ILogger<RiskBundleJob> _logger;
public RiskBundleJob(
RiskBundleBuilder builder,
IRiskBundleManifestSigner signer,
IRiskBundleArchiveSigner archiveSigner,
IRiskBundleObjectStore objectStore,
ILogger<RiskBundleJob> logger)
{
_builder = builder ?? throw new ArgumentNullException(nameof(builder));
_signer = signer ?? throw new ArgumentNullException(nameof(signer));
_archiveSigner = archiveSigner ?? throw new ArgumentNullException(nameof(archiveSigner));
_objectStore = objectStore ?? throw new ArgumentNullException(nameof(objectStore));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<RiskBundleJobOutcome> ExecuteAsync(RiskBundleJobRequest request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
cancellationToken.ThrowIfCancellationRequested();
var bundleFileName = string.IsNullOrWhiteSpace(request.BundleFileName)
? "risk-bundle.tar.gz"
: request.BundleFileName;
var prepared = _builder.Prepare(request.BuildRequest, cancellationToken);
_logger.LogInformation("Risk bundle built with {ProviderCount} providers (prepared).", prepared.Manifest.Providers.Count);
var manifestSignature = await _signer.SignAsync(prepared.ManifestJson, cancellationToken).ConfigureAwait(false);
var signatureJson = JsonSerializer.Serialize(manifestSignature, SerializerOptions);
var additionalFiles = new[]
{
RiskBundleAdditionalFile.FromText($"signatures/{request.BuildRequest.ManifestDsseFileName}", signatureJson)
};
var build = _builder.Build(request.BuildRequest, additionalFiles, prepared, cancellationToken);
var bundleSignature = await _archiveSigner.SignArchiveAsync(build.BundleStream, cancellationToken).ConfigureAwait(false);
var bundleSignatureFileName = $"{bundleFileName}.sig";
var manifestKey = Combine(request.StoragePrefix, request.BuildRequest.ManifestFileName);
var manifestSigKey = Combine(request.StoragePrefix, $"signatures/{request.BuildRequest.ManifestDsseFileName}");
var bundleKey = Combine(request.StoragePrefix, bundleFileName);
var bundleSigKey = Combine(request.StoragePrefix, $"signatures/{bundleSignatureFileName}");
var manifestStorage = await _objectStore.StoreAsync(
new RiskBundleObjectStoreOptions(manifestKey, "application/json"),
new MemoryStream(Encoding.UTF8.GetBytes(build.ManifestJson)),
cancellationToken).ConfigureAwait(false);
var signatureStorage = await _objectStore.StoreAsync(
new RiskBundleObjectStoreOptions(manifestSigKey, "application/json"),
new MemoryStream(Encoding.UTF8.GetBytes(signatureJson)),
cancellationToken).ConfigureAwait(false);
build.BundleStream.Position = 0;
var bundleStorage = await _objectStore.StoreAsync(
new RiskBundleObjectStoreOptions(bundleKey, "application/gzip"),
build.BundleStream,
cancellationToken).ConfigureAwait(false);
var bundleSignatureStorage = await _objectStore.StoreAsync(
new RiskBundleObjectStoreOptions(bundleSigKey, "application/vnd.stellaops.signature"),
new MemoryStream(Encoding.UTF8.GetBytes(bundleSignature)),
cancellationToken).ConfigureAwait(false);
return new RiskBundleJobOutcome(
build.Manifest,
manifestStorage,
signatureStorage,
bundleStorage,
bundleSignatureStorage,
build.ManifestJson,
signatureJson,
bundleSignature,
build.RootHash);
}
private static string Combine(string prefix, string fileName)
{
if (string.IsNullOrWhiteSpace(prefix))
{
return fileName;
}
var sanitizedPrefix = prefix.Trim('/');
return string.IsNullOrWhiteSpace(sanitizedPrefix)
? fileName
: $"{sanitizedPrefix}/{fileName}";
}
private static readonly JsonSerializerOptions SerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false
};
}