Implement MongoDB-based storage for Pack Run approval, artifact, log, and state management
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Added MongoPackRunApprovalStore for managing approval states with MongoDB. - Introduced MongoPackRunArtifactUploader for uploading and storing artifacts. - Created MongoPackRunLogStore to handle logging of pack run events. - Developed MongoPackRunStateStore for persisting and retrieving pack run states. - Implemented unit tests for MongoDB stores to ensure correct functionality. - Added MongoTaskRunnerTestContext for setting up MongoDB test environment. - Enhanced PackRunStateFactory to correctly initialize state with gate reasons.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Scanner.Sbomer.BuildXPlugin.Surface;
|
||||
|
||||
internal static class SurfaceCasLayout
|
||||
{
|
||||
internal const string DefaultBucket = "scanner-artifacts";
|
||||
internal const string DefaultRootPrefix = "scanner";
|
||||
private const string Sha256 = "sha256";
|
||||
|
||||
public static string NormalizeDigest(string? digest)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(digest))
|
||||
{
|
||||
throw new BuildxPluginException("Surface artefact digest cannot be empty.");
|
||||
}
|
||||
|
||||
var trimmed = digest.Trim();
|
||||
return trimmed.Contains(':', StringComparison.Ordinal)
|
||||
? trimmed
|
||||
: $"{Sha256}:{trimmed}";
|
||||
}
|
||||
|
||||
public static string ExtractDigestValue(string normalizedDigest)
|
||||
{
|
||||
var parts = normalizedDigest.Split(':', 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
return parts.Length == 2 ? parts[1] : normalizedDigest;
|
||||
}
|
||||
|
||||
public static string BuildObjectKey(string rootPrefix, SurfaceCasKind kind, string normalizedDigest)
|
||||
{
|
||||
var digestValue = ExtractDigestValue(normalizedDigest);
|
||||
var prefix = kind switch
|
||||
{
|
||||
SurfaceCasKind.LayerFragments => "surface/payloads/layer-fragments",
|
||||
SurfaceCasKind.EntryTraceGraph => "surface/payloads/entrytrace",
|
||||
SurfaceCasKind.EntryTraceNdjson => "surface/payloads/entrytrace",
|
||||
SurfaceCasKind.Manifest => "surface/manifests",
|
||||
_ => "surface/unknown"
|
||||
};
|
||||
|
||||
var extension = kind switch
|
||||
{
|
||||
SurfaceCasKind.LayerFragments => "layer-fragments.json",
|
||||
SurfaceCasKind.EntryTraceGraph => "entrytrace.graph.json",
|
||||
SurfaceCasKind.EntryTraceNdjson => "entrytrace.ndjson",
|
||||
SurfaceCasKind.Manifest => "surface.manifest.json",
|
||||
_ => "artifact.bin"
|
||||
};
|
||||
|
||||
var normalizedRoot = string.IsNullOrWhiteSpace(rootPrefix)
|
||||
? string.Empty
|
||||
: rootPrefix.Trim().Trim('/');
|
||||
|
||||
var relative = $"{prefix}/{digestValue}/{extension}";
|
||||
return string.IsNullOrWhiteSpace(normalizedRoot) ? relative : $"{normalizedRoot}/{relative}";
|
||||
}
|
||||
|
||||
public static string BuildCasUri(string bucket, string objectKey)
|
||||
{
|
||||
var normalizedBucket = string.IsNullOrWhiteSpace(bucket) ? DefaultBucket : bucket.Trim();
|
||||
var normalizedKey = string.IsNullOrWhiteSpace(objectKey) ? string.Empty : objectKey.Trim().TrimStart('/');
|
||||
return $"cas://{normalizedBucket}/{normalizedKey}";
|
||||
}
|
||||
|
||||
public static string ComputeDigest(ReadOnlySpan<byte> content)
|
||||
{
|
||||
Span<byte> hash = stackalloc byte[32];
|
||||
SHA256.HashData(content, hash);
|
||||
return $"{Sha256}:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||
}
|
||||
|
||||
public static async Task<string> WriteBytesAsync(string rootDirectory, string objectKey, byte[] bytes, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rootDirectory))
|
||||
{
|
||||
throw new BuildxPluginException("Surface cache root must be provided.");
|
||||
}
|
||||
|
||||
var normalizedRoot = Path.GetFullPath(rootDirectory);
|
||||
var relativePath = objectKey.Replace('/', Path.DirectorySeparatorChar);
|
||||
var fullPath = Path.Combine(normalizedRoot, relativePath);
|
||||
var directory = Path.GetDirectoryName(fullPath);
|
||||
if (!string.IsNullOrWhiteSpace(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
await using var stream = new FileStream(
|
||||
fullPath,
|
||||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
FileShare.Read,
|
||||
bufferSize: 64 * 1024,
|
||||
options: FileOptions.Asynchronous | FileOptions.SequentialScan);
|
||||
await stream.WriteAsync(bytes.AsMemory(0, bytes.Length), cancellationToken).ConfigureAwait(false);
|
||||
await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum SurfaceCasKind
|
||||
{
|
||||
LayerFragments,
|
||||
EntryTraceGraph,
|
||||
EntryTraceNdjson,
|
||||
Manifest
|
||||
}
|
||||
Reference in New Issue
Block a user