up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Reachability Corpus Validation / validate-corpus (push) Has been cancelled
Reachability Corpus Validation / validate-ground-truths (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Reachability Corpus Validation / determinism-check (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Reachability Corpus Validation / validate-corpus (push) Has been cancelled
Reachability Corpus Validation / validate-ground-truths (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Reachability Corpus Validation / determinism-check (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
This commit is contained in:
@@ -1,9 +1,15 @@
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Scanner.Analyzers.Lang.DotNet.Internal.Bundling;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves publish artifacts (deps/runtimeconfig) into deterministic entrypoint identities.
|
||||
/// Per SCANNER-ANALYZERS-LANG-11-001: maps project/publish artifacts to normalized entrypoint records
|
||||
/// with assembly name, MVID, TFM, RID, host kind, publish mode, ALC hints, and probing paths.
|
||||
/// </summary>
|
||||
public static class DotNetEntrypointResolver
|
||||
{
|
||||
@@ -46,6 +52,7 @@ public static class DotNetEntrypointResolver
|
||||
}
|
||||
|
||||
var name = GetEntrypointName(depsPath);
|
||||
var directory = Path.GetDirectoryName(depsPath) ?? ".";
|
||||
|
||||
DotNetRuntimeConfig? runtimeConfig = null;
|
||||
var runtimeConfigPath = GetRuntimeConfigPath(depsPath, name);
|
||||
@@ -61,16 +68,51 @@ public static class DotNetEntrypointResolver
|
||||
var rids = CollectRuntimeIdentifiers(depsFile, runtimeConfig);
|
||||
var publishKind = DeterminePublishKind(depsFile);
|
||||
|
||||
var id = BuildDeterministicId(name, tfms, rids, publishKind);
|
||||
// Resolve assembly and apphost paths
|
||||
var (assemblyPath, apphostPath) = ResolveEntrypointPaths(directory, name);
|
||||
|
||||
// Extract MVID from PE header (11-001 requirement)
|
||||
var mvid = ExtractMvid(assemblyPath);
|
||||
|
||||
// Compute SHA-256 hash over assembly bytes (11-001 requirement)
|
||||
var (hash, fileSize) = ComputeHashAndSize(assemblyPath);
|
||||
|
||||
// Determine host kind: apphost, framework-dependent, self-contained (11-001 requirement)
|
||||
var hostKind = DetermineHostKind(apphostPath, publishKind);
|
||||
|
||||
// Determine publish mode: single-file, trimmed, normal (11-001 requirement)
|
||||
var publishMode = DeterminePublishMode(apphostPath, depsFile, directory);
|
||||
|
||||
// Collect ALC hints from runtimeconfig.dev.json (11-001 requirement)
|
||||
var alcHints = CollectAlcHints(directory, name);
|
||||
|
||||
// Collect probing paths from runtimeconfig files (11-001 requirement)
|
||||
var probingPaths = CollectProbingPaths(directory, name);
|
||||
|
||||
// Collect native dependencies for apphost bundles (11-001 requirement)
|
||||
var nativeDeps = CollectNativeDependencies(apphostPath, publishMode);
|
||||
|
||||
var id = BuildDeterministicId(name, tfms, rids, publishKind, mvid);
|
||||
|
||||
results.Add(new DotNetEntrypoint(
|
||||
Id: id,
|
||||
Name: name,
|
||||
AssemblyName: Path.GetFileName(assemblyPath ?? $"{name}.dll"),
|
||||
Mvid: mvid,
|
||||
TargetFrameworks: tfms,
|
||||
RuntimeIdentifiers: rids,
|
||||
HostKind: hostKind,
|
||||
PublishKind: publishKind,
|
||||
PublishMode: publishMode,
|
||||
AlcHints: alcHints,
|
||||
ProbingPaths: probingPaths,
|
||||
NativeDependencies: nativeDeps,
|
||||
Hash: hash,
|
||||
FileSizeBytes: fileSize,
|
||||
RelativeDepsPath: relativeDepsPath,
|
||||
RelativeRuntimeConfigPath: relativeRuntimeConfig,
|
||||
PublishKind: publishKind));
|
||||
RelativeAssemblyPath: assemblyPath is not null ? NormalizeRelative(context.GetRelativePath(assemblyPath)) : null,
|
||||
RelativeApphostPath: apphostPath is not null ? NormalizeRelative(context.GetRelativePath(apphostPath)) : null));
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
@@ -89,6 +131,292 @@ public static class DotNetEntrypointResolver
|
||||
return ValueTask.FromResult<IReadOnlyList<DotNetEntrypoint>>(results);
|
||||
}
|
||||
|
||||
private static (string? assemblyPath, string? apphostPath) ResolveEntrypointPaths(string directory, string name)
|
||||
{
|
||||
string? assemblyPath = null;
|
||||
string? apphostPath = null;
|
||||
|
||||
// Look for main assembly (.dll)
|
||||
var dllPath = Path.Combine(directory, $"{name}.dll");
|
||||
if (File.Exists(dllPath))
|
||||
{
|
||||
assemblyPath = dllPath;
|
||||
}
|
||||
|
||||
// Look for apphost executable (.exe on Windows, no extension on Unix)
|
||||
var exePath = Path.Combine(directory, $"{name}.exe");
|
||||
if (File.Exists(exePath))
|
||||
{
|
||||
apphostPath = exePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check for Unix-style executable (no extension)
|
||||
var unixExePath = Path.Combine(directory, name);
|
||||
if (File.Exists(unixExePath) && !unixExePath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
apphostPath = unixExePath;
|
||||
}
|
||||
}
|
||||
|
||||
return (assemblyPath, apphostPath);
|
||||
}
|
||||
|
||||
private static Guid? ExtractMvid(string? assemblyPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assemblyPath) || !File.Exists(assemblyPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = File.OpenRead(assemblyPath);
|
||||
using var peReader = new PEReader(stream);
|
||||
|
||||
if (!peReader.HasMetadata)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var metadataReader = peReader.GetMetadataReader();
|
||||
var moduleDefinition = metadataReader.GetModuleDefinition();
|
||||
return metadataReader.GetGuid(moduleDefinition.Mvid);
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static (string? hash, long fileSize) ComputeHashAndSize(string? assemblyPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assemblyPath) || !File.Exists(assemblyPath))
|
||||
{
|
||||
return (null, 0);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = File.OpenRead(assemblyPath);
|
||||
var fileSize = stream.Length;
|
||||
var hashBytes = SHA256.HashData(stream);
|
||||
var hash = $"sha256:{Convert.ToHexStringLower(hashBytes)}";
|
||||
return (hash, fileSize);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return (null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static DotNetHostKind DetermineHostKind(string? apphostPath, DotNetPublishKind publishKind)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(apphostPath) && File.Exists(apphostPath))
|
||||
{
|
||||
return DotNetHostKind.Apphost;
|
||||
}
|
||||
|
||||
return publishKind switch
|
||||
{
|
||||
DotNetPublishKind.SelfContained => DotNetHostKind.SelfContained,
|
||||
DotNetPublishKind.FrameworkDependent => DotNetHostKind.FrameworkDependent,
|
||||
_ => DotNetHostKind.Unknown
|
||||
};
|
||||
}
|
||||
|
||||
private static DotNetPublishMode DeterminePublishMode(string? apphostPath, DotNetDepsFile depsFile, string directory)
|
||||
{
|
||||
// Check for single-file bundle
|
||||
if (!string.IsNullOrEmpty(apphostPath) && File.Exists(apphostPath))
|
||||
{
|
||||
var singleFileResult = SingleFileAppDetector.Analyze(apphostPath);
|
||||
if (singleFileResult.IsSingleFile)
|
||||
{
|
||||
return DotNetPublishMode.SingleFile;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for trimmed publish (look for trim markers or reduced dependency count)
|
||||
var trimmedMarkerPath = Path.Combine(directory, $"{Path.GetFileNameWithoutExtension(apphostPath ?? "app")}.staticwebassets.runtime.json");
|
||||
if (File.Exists(trimmedMarkerPath))
|
||||
{
|
||||
return DotNetPublishMode.Trimmed;
|
||||
}
|
||||
|
||||
// Check deps.json for trimmed indicators
|
||||
foreach (var library in depsFile.Libraries.Values)
|
||||
{
|
||||
if (library.Id.Contains("ILLink", StringComparison.OrdinalIgnoreCase) ||
|
||||
library.Id.Contains("Trimmer", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DotNetPublishMode.Trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
return DotNetPublishMode.Normal;
|
||||
}
|
||||
|
||||
private static IReadOnlyCollection<string> CollectAlcHints(string directory, string name)
|
||||
{
|
||||
var hints = new SortedSet<string>(StringComparer.Ordinal);
|
||||
|
||||
// Check runtimeconfig.dev.json for ALC hints
|
||||
var devConfigPath = Path.Combine(directory, $"{name}.runtimeconfig.dev.json");
|
||||
if (File.Exists(devConfigPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(devConfigPath);
|
||||
using var doc = JsonDocument.Parse(json, new JsonDocumentOptions
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
CommentHandling = JsonCommentHandling.Skip
|
||||
});
|
||||
|
||||
if (doc.RootElement.TryGetProperty("runtimeOptions", out var runtimeOptions))
|
||||
{
|
||||
// Look for additionalProbingPaths which indicate ALC usage
|
||||
if (runtimeOptions.TryGetProperty("additionalProbingPaths", out var probingPaths) &&
|
||||
probingPaths.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var path in probingPaths.EnumerateArray())
|
||||
{
|
||||
if (path.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var pathValue = path.GetString();
|
||||
if (!string.IsNullOrWhiteSpace(pathValue))
|
||||
{
|
||||
// Extract ALC hint from path pattern
|
||||
if (pathValue.Contains(".nuget", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
hints.Add("NuGetAssemblyLoadContext");
|
||||
}
|
||||
else if (pathValue.Contains("sdk", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
hints.Add("SdkAssemblyLoadContext");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// Ignore malformed dev config
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Ignore read errors
|
||||
}
|
||||
}
|
||||
|
||||
// Add default ALC hint
|
||||
if (hints.Count == 0)
|
||||
{
|
||||
hints.Add("Default");
|
||||
}
|
||||
|
||||
return hints;
|
||||
}
|
||||
|
||||
private static IReadOnlyCollection<string> CollectProbingPaths(string directory, string name)
|
||||
{
|
||||
var paths = new SortedSet<string>(StringComparer.Ordinal);
|
||||
|
||||
// Check runtimeconfig.dev.json for probing paths
|
||||
var devConfigPath = Path.Combine(directory, $"{name}.runtimeconfig.dev.json");
|
||||
if (File.Exists(devConfigPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(devConfigPath);
|
||||
using var doc = JsonDocument.Parse(json, new JsonDocumentOptions
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
CommentHandling = JsonCommentHandling.Skip
|
||||
});
|
||||
|
||||
if (doc.RootElement.TryGetProperty("runtimeOptions", out var runtimeOptions) &&
|
||||
runtimeOptions.TryGetProperty("additionalProbingPaths", out var probingPaths) &&
|
||||
probingPaths.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var path in probingPaths.EnumerateArray())
|
||||
{
|
||||
if (path.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var pathValue = path.GetString();
|
||||
if (!string.IsNullOrWhiteSpace(pathValue))
|
||||
{
|
||||
// Normalize and add the probing path
|
||||
paths.Add(NormalizeRelative(pathValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// Ignore malformed dev config
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Ignore read errors
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
private static IReadOnlyCollection<string> CollectNativeDependencies(string? apphostPath, DotNetPublishMode publishMode)
|
||||
{
|
||||
var nativeDeps = new SortedSet<string>(StringComparer.Ordinal);
|
||||
|
||||
if (publishMode != DotNetPublishMode.SingleFile || string.IsNullOrEmpty(apphostPath))
|
||||
{
|
||||
return nativeDeps;
|
||||
}
|
||||
|
||||
// For single-file apps, try to extract bundled native library names
|
||||
// This is a simplified detection - full extraction would require parsing the bundle manifest
|
||||
var directory = Path.GetDirectoryName(apphostPath);
|
||||
if (string.IsNullOrEmpty(directory))
|
||||
{
|
||||
return nativeDeps;
|
||||
}
|
||||
|
||||
// Look for extracted native libraries (some single-file apps extract natives at runtime)
|
||||
var nativePatterns = new[] { "*.so", "*.dylib", "*.dll" };
|
||||
foreach (var pattern in nativePatterns)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var nativePath in Directory.EnumerateFiles(directory, pattern))
|
||||
{
|
||||
var fileName = Path.GetFileName(nativePath);
|
||||
// Filter out managed assemblies
|
||||
if (!fileName.Equals(Path.GetFileName(apphostPath), StringComparison.OrdinalIgnoreCase) &&
|
||||
!fileName.EndsWith(".deps.json", StringComparison.OrdinalIgnoreCase) &&
|
||||
!fileName.EndsWith(".runtimeconfig.json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
nativeDeps.Add(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Ignore enumeration errors
|
||||
}
|
||||
}
|
||||
|
||||
return nativeDeps;
|
||||
}
|
||||
|
||||
private static string GetEntrypointName(string depsPath)
|
||||
{
|
||||
// Strip .json then any trailing .deps suffix to yield a logical entrypoint name.
|
||||
@@ -273,12 +601,14 @@ public static class DotNetEntrypointResolver
|
||||
string name,
|
||||
IReadOnlyCollection<string> tfms,
|
||||
IReadOnlyCollection<string> rids,
|
||||
DotNetPublishKind publishKind)
|
||||
DotNetPublishKind publishKind,
|
||||
Guid? mvid)
|
||||
{
|
||||
var tfmPart = tfms.Count == 0 ? "unknown" : string.Join('+', tfms.OrderBy(t => t, StringComparer.OrdinalIgnoreCase));
|
||||
var ridPart = rids.Count == 0 ? "none" : string.Join('+', rids.OrderBy(r => r, StringComparer.OrdinalIgnoreCase));
|
||||
var publishPart = publishKind.ToString().ToLowerInvariant();
|
||||
return $"{name}:{tfmPart}:{ridPart}:{publishPart}";
|
||||
var mvidPart = mvid?.ToString("N") ?? "no-mvid";
|
||||
return $"{name}:{tfmPart}:{ridPart}:{publishPart}:{mvidPart}";
|
||||
}
|
||||
|
||||
private static string NormalizeRelative(string path)
|
||||
@@ -293,18 +623,84 @@ public static class DotNetEntrypointResolver
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a resolved .NET entrypoint with deterministic identity per SCANNER-ANALYZERS-LANG-11-001.
|
||||
/// </summary>
|
||||
public sealed record DotNetEntrypoint(
|
||||
/// <summary>Deterministic identifier: name:tfms:rids:publishKind:mvid</summary>
|
||||
string Id,
|
||||
/// <summary>Logical entrypoint name derived from deps.json</summary>
|
||||
string Name,
|
||||
/// <summary>Assembly file name (e.g., "MyApp.dll")</summary>
|
||||
string AssemblyName,
|
||||
/// <summary>Module Version ID from PE metadata (deterministic per build)</summary>
|
||||
Guid? Mvid,
|
||||
/// <summary>Target frameworks (normalized, e.g., "net8.0")</summary>
|
||||
IReadOnlyCollection<string> TargetFrameworks,
|
||||
/// <summary>Runtime identifiers (e.g., "linux-x64", "win-x64")</summary>
|
||||
IReadOnlyCollection<string> RuntimeIdentifiers,
|
||||
/// <summary>Host kind: apphost, framework-dependent, self-contained</summary>
|
||||
DotNetHostKind HostKind,
|
||||
/// <summary>Publish kind from deps.json analysis</summary>
|
||||
DotNetPublishKind PublishKind,
|
||||
/// <summary>Publish mode: normal, single-file, trimmed</summary>
|
||||
DotNetPublishMode PublishMode,
|
||||
/// <summary>AssemblyLoadContext hints from runtimeconfig.dev.json</summary>
|
||||
IReadOnlyCollection<string> AlcHints,
|
||||
/// <summary>Additional probing paths from runtimeconfig.dev.json</summary>
|
||||
IReadOnlyCollection<string> ProbingPaths,
|
||||
/// <summary>Native dependencies for single-file bundles</summary>
|
||||
IReadOnlyCollection<string> NativeDependencies,
|
||||
/// <summary>SHA-256 hash of assembly bytes (sha256:hex)</summary>
|
||||
string? Hash,
|
||||
/// <summary>Assembly file size in bytes</summary>
|
||||
long FileSizeBytes,
|
||||
/// <summary>Relative path to deps.json</summary>
|
||||
string RelativeDepsPath,
|
||||
/// <summary>Relative path to runtimeconfig.json</summary>
|
||||
string? RelativeRuntimeConfigPath,
|
||||
DotNetPublishKind PublishKind);
|
||||
/// <summary>Relative path to main assembly (.dll)</summary>
|
||||
string? RelativeAssemblyPath,
|
||||
/// <summary>Relative path to apphost executable</summary>
|
||||
string? RelativeApphostPath);
|
||||
|
||||
/// <summary>
|
||||
/// .NET host kind classification per SCANNER-ANALYZERS-LANG-11-001.
|
||||
/// </summary>
|
||||
public enum DotNetHostKind
|
||||
{
|
||||
/// <summary>Host kind could not be determined</summary>
|
||||
Unknown = 0,
|
||||
/// <summary>Application uses apphost executable</summary>
|
||||
Apphost = 1,
|
||||
/// <summary>Framework-dependent deployment (requires shared runtime)</summary>
|
||||
FrameworkDependent = 2,
|
||||
/// <summary>Self-contained deployment (includes runtime)</summary>
|
||||
SelfContained = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// .NET publish kind from deps.json analysis.
|
||||
/// </summary>
|
||||
public enum DotNetPublishKind
|
||||
{
|
||||
/// <summary>Publish kind could not be determined</summary>
|
||||
Unknown = 0,
|
||||
/// <summary>Framework-dependent (relies on shared .NET runtime)</summary>
|
||||
FrameworkDependent = 1,
|
||||
/// <summary>Self-contained (includes .NET runtime)</summary>
|
||||
SelfContained = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// .NET publish mode per SCANNER-ANALYZERS-LANG-11-001.
|
||||
/// </summary>
|
||||
public enum DotNetPublishMode
|
||||
{
|
||||
/// <summary>Normal publish (separate files)</summary>
|
||||
Normal = 0,
|
||||
/// <summary>Single-file publish (assemblies bundled into executable)</summary>
|
||||
SingleFile = 1,
|
||||
/// <summary>Trimmed publish (unused code removed)</summary>
|
||||
Trimmed = 2
|
||||
}
|
||||
|
||||
@@ -204,6 +204,59 @@ public sealed class RuntimeEventRepository : RepositoryBase<ScannerDataSource>
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public Task<RuntimeEventDocument?> GetByEventIdAsync(string eventId, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(eventId);
|
||||
|
||||
var sql = $"""
|
||||
SELECT id, event_id, schema_version, tenant, node, kind, "when", received_at, expires_at,
|
||||
platform, namespace, pod, container, container_id, image_ref, image_digest,
|
||||
engine, engine_version, baseline_digest, image_signed, sbom_referrer, build_id, payload
|
||||
FROM {Table}
|
||||
WHERE event_id = @event_id
|
||||
""";
|
||||
|
||||
return QuerySingleOrDefaultAsync(
|
||||
Tenant,
|
||||
sql,
|
||||
cmd => AddParameter(cmd, "event_id", eventId),
|
||||
MapRuntimeEvent,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<RuntimeEventDocument>> GetByImageDigestAsync(
|
||||
string imageDigest,
|
||||
int limit,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(imageDigest);
|
||||
if (limit <= 0)
|
||||
{
|
||||
limit = 100;
|
||||
}
|
||||
|
||||
var sql = $"""
|
||||
SELECT id, event_id, schema_version, tenant, node, kind, "when", received_at, expires_at,
|
||||
platform, namespace, pod, container, container_id, image_ref, image_digest,
|
||||
engine, engine_version, baseline_digest, image_signed, sbom_referrer, build_id, payload
|
||||
FROM {Table}
|
||||
WHERE image_digest = @image_digest
|
||||
ORDER BY received_at DESC
|
||||
LIMIT @limit
|
||||
""";
|
||||
|
||||
return await QueryAsync(
|
||||
Tenant,
|
||||
sql,
|
||||
cmd =>
|
||||
{
|
||||
AddParameter(cmd, "image_digest", imageDigest.Trim().ToLowerInvariant());
|
||||
AddParameter(cmd, "limit", limit);
|
||||
},
|
||||
MapRuntimeEvent,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static RuntimeEventDocument MapRuntimeEvent(NpgsqlDataReader reader)
|
||||
{
|
||||
var payloadOrdinal = reader.GetOrdinal("payload");
|
||||
|
||||
Reference in New Issue
Block a user