84 lines
2.8 KiB
C#
84 lines
2.8 KiB
C#
|
|
using StellaOps.Scanner.Cache.Abstractions;
|
|
using System;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace StellaOps.Scanner.Reachability;
|
|
|
|
/// <summary>
|
|
/// Packages a reachability union graph into a deterministic zip, stores it in CAS, and returns the CAS reference.
|
|
/// </summary>
|
|
public sealed class ReachabilityUnionPublisher
|
|
{
|
|
private readonly ReachabilityUnionWriter writer;
|
|
|
|
public ReachabilityUnionPublisher(ReachabilityUnionWriter writer)
|
|
{
|
|
this.writer = writer ?? throw new ArgumentNullException(nameof(writer));
|
|
}
|
|
|
|
public async Task<ReachabilityUnionPublishResult> PublishAsync(
|
|
ReachabilityUnionGraph graph,
|
|
IFileContentAddressableStore cas,
|
|
string workRoot,
|
|
string analysisId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(graph);
|
|
ArgumentNullException.ThrowIfNull(cas);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(workRoot);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(analysisId);
|
|
|
|
var result = await writer.WriteAsync(graph, workRoot, analysisId, cancellationToken).ConfigureAwait(false);
|
|
|
|
var folder = Path.GetDirectoryName(result.MetaPath)!;
|
|
var zipPath = Path.Combine(folder, "reachability.zip");
|
|
CreateZip(folder, zipPath);
|
|
|
|
var sha = ComputeSha256(zipPath);
|
|
await using var zipStream = File.OpenRead(zipPath);
|
|
var casEntry = await cas.PutAsync(new FileCasPutRequest(sha, zipStream, leaveOpen: false), cancellationToken).ConfigureAwait(false);
|
|
|
|
return new ReachabilityUnionPublishResult(
|
|
Sha256: sha,
|
|
RelativePath: casEntry.RelativePath,
|
|
Records: result.Nodes.RecordCount + result.Edges.RecordCount + (result.Facts?.RecordCount ?? 0));
|
|
}
|
|
|
|
private static void CreateZip(string sourceDir, string destinationZip)
|
|
{
|
|
if (File.Exists(destinationZip))
|
|
{
|
|
File.Delete(destinationZip);
|
|
}
|
|
|
|
var files = Directory.EnumerateFiles(sourceDir, "*", SearchOption.TopDirectoryOnly)
|
|
.OrderBy(f => f, StringComparer.Ordinal)
|
|
.ToList();
|
|
|
|
using var zip = ZipFile.Open(destinationZip, ZipArchiveMode.Create);
|
|
foreach (var file in files)
|
|
{
|
|
var entryName = Path.GetFileName(file);
|
|
zip.CreateEntryFromFile(file, entryName, CompressionLevel.Optimal);
|
|
}
|
|
}
|
|
|
|
private static string ComputeSha256(string path)
|
|
{
|
|
using var sha = SHA256.Create();
|
|
using var stream = File.OpenRead(path);
|
|
return Convert.ToHexString(sha.ComputeHash(stream)).ToLowerInvariant();
|
|
}
|
|
}
|
|
|
|
public sealed record ReachabilityUnionPublishResult(
|
|
string Sha256,
|
|
string RelativePath,
|
|
int Records);
|