132 lines
5.4 KiB
C#
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
|
|
};
|
|
}
|