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.
113 lines
4.1 KiB
C#
113 lines
4.1 KiB
C#
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
|
|
}
|