Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.Delta/IDeltaLayerScanner.cs

271 lines
7.5 KiB
C#

using System.Collections.Immutable;
namespace StellaOps.Scanner.Delta;
/// <summary>
/// Scans only changed layers between two image versions for efficient delta scanning.
/// </summary>
public interface IDeltaLayerScanner
{
/// <summary>
/// Performs a delta scan comparing two image versions.
/// </summary>
/// <param name="oldImage">Reference to the old/baseline image.</param>
/// <param name="newImage">Reference to the new image to scan.</param>
/// <param name="options">Delta scan options.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Delta scan result with changed layers and composite SBOM.</returns>
Task<DeltaScanResult> ScanDeltaAsync(
string oldImage,
string newImage,
DeltaScanOptions? options = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Identifies layer changes between two images without scanning.
/// </summary>
/// <param name="oldImage">Reference to the old image.</param>
/// <param name="newImage">Reference to the new image.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Layer change summary.</returns>
Task<LayerChangeSummary> IdentifyLayerChangesAsync(
string oldImage,
string newImage,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Options for delta scanning.
/// </summary>
public sealed record DeltaScanOptions
{
/// <summary>
/// Whether to force a full scan even for unchanged layers.
/// Default is false.
/// </summary>
public bool ForceFullScan { get; init; } = false;
/// <summary>
/// Whether to use cached per-layer SBOMs for unchanged layers.
/// Default is true.
/// </summary>
public bool UseCachedSboms { get; init; } = true;
/// <summary>
/// Maximum age of cached SBOMs to consider valid (in days).
/// Default is 30 days.
/// </summary>
public int MaxCacheAgeDays { get; init; } = 30;
/// <summary>
/// SBOM format to produce (CycloneDX or SPDX).
/// Default is CycloneDX.
/// </summary>
public string SbomFormat { get; init; } = "cyclonedx";
/// <summary>
/// Whether to include layer attribution in the SBOM.
/// Default is true.
/// </summary>
public bool IncludeLayerAttribution { get; init; } = true;
/// <summary>
/// Target platform for multi-arch images.
/// If null, uses default platform.
/// </summary>
public string? Platform { get; init; }
}
/// <summary>
/// Result of a delta scan operation.
/// </summary>
public sealed record DeltaScanResult
{
/// <summary>
/// Old image reference.
/// </summary>
public required string OldImage { get; init; }
/// <summary>
/// Old image manifest digest.
/// </summary>
public required string OldManifestDigest { get; init; }
/// <summary>
/// New image reference.
/// </summary>
public required string NewImage { get; init; }
/// <summary>
/// New image manifest digest.
/// </summary>
public required string NewManifestDigest { get; init; }
/// <summary>
/// Layers that were added in the new image.
/// </summary>
public ImmutableArray<LayerChangeInfo> AddedLayers { get; init; } = [];
/// <summary>
/// Layers that were removed from the old image.
/// </summary>
public ImmutableArray<LayerChangeInfo> RemovedLayers { get; init; } = [];
/// <summary>
/// Layers that are unchanged between images.
/// </summary>
public ImmutableArray<LayerChangeInfo> UnchangedLayers { get; init; } = [];
/// <summary>
/// Composite SBOM for the new image (combining cached + newly scanned).
/// </summary>
public string? CompositeSbom { get; init; }
/// <summary>
/// SBOM format (cyclonedx or spdx).
/// </summary>
public string? SbomFormat { get; init; }
/// <summary>
/// Total scan duration.
/// </summary>
public TimeSpan ScanDuration { get; init; }
/// <summary>
/// Duration spent scanning only added layers.
/// </summary>
public TimeSpan AddedLayersScanDuration { get; init; }
/// <summary>
/// Number of components found in added layers.
/// </summary>
public int AddedComponentCount { get; init; }
/// <summary>
/// Number of components from cached layers.
/// </summary>
public int CachedComponentCount { get; init; }
/// <summary>
/// Whether the scan used cached SBOMs.
/// </summary>
public bool UsedCache { get; init; }
/// <summary>
/// Percentage of layers that were reused from cache.
/// </summary>
public double LayerReuseRatio =>
(AddedLayers.Length + UnchangedLayers.Length) > 0
? (double)UnchangedLayers.Length / (AddedLayers.Length + UnchangedLayers.Length)
: 0;
/// <summary>
/// When the scan was performed.
/// </summary>
public DateTimeOffset ScannedAt { get; init; } = DateTimeOffset.UtcNow;
}
/// <summary>
/// Information about a layer change.
/// </summary>
public sealed record LayerChangeInfo
{
/// <summary>
/// Compressed layer digest.
/// </summary>
public required string LayerDigest { get; init; }
/// <summary>
/// Uncompressed content hash (diffID).
/// </summary>
public required string DiffId { get; init; }
/// <summary>
/// Layer index in the image.
/// </summary>
public int LayerIndex { get; init; }
/// <summary>
/// Size of the layer in bytes.
/// </summary>
public long Size { get; init; }
/// <summary>
/// Media type of the layer.
/// </summary>
public string? MediaType { get; init; }
/// <summary>
/// Whether this layer's SBOM was retrieved from cache.
/// </summary>
public bool FromCache { get; init; }
/// <summary>
/// Number of components found in this layer.
/// </summary>
public int ComponentCount { get; init; }
}
/// <summary>
/// Summary of layer changes between two images.
/// </summary>
public sealed record LayerChangeSummary
{
/// <summary>
/// Old image reference.
/// </summary>
public required string OldImage { get; init; }
/// <summary>
/// New image reference.
/// </summary>
public required string NewImage { get; init; }
/// <summary>
/// Total layers in old image.
/// </summary>
public int OldLayerCount { get; init; }
/// <summary>
/// Total layers in new image.
/// </summary>
public int NewLayerCount { get; init; }
/// <summary>
/// Number of layers added.
/// </summary>
public int AddedCount { get; init; }
/// <summary>
/// Number of layers removed.
/// </summary>
public int RemovedCount { get; init; }
/// <summary>
/// Number of layers unchanged.
/// </summary>
public int UnchangedCount { get; init; }
/// <summary>
/// Estimated scan savings (layers we don't need to scan).
/// </summary>
public double EstimatedSavingsRatio => NewLayerCount > 0
? (double)UnchangedCount / NewLayerCount
: 0;
/// <summary>
/// DiffIDs of added layers.
/// </summary>
public ImmutableArray<string> AddedDiffIds { get; init; } = [];
/// <summary>
/// DiffIDs of removed layers.
/// </summary>
public ImmutableArray<string> RemovedDiffIds { get; init; } = [];
/// <summary>
/// DiffIDs of unchanged layers.
/// </summary>
public ImmutableArray<string> UnchangedDiffIds { get; init; } = [];
}