using System.Collections.Immutable; using StellaOps.Concelier.SbomIntegration.Models; using StellaOps.Scanner.BuildProvenance.Models; namespace StellaOps.Scanner.BuildProvenance.Analyzers; public sealed class BuildProvenanceChainBuilder { private static readonly string[] BuilderIdKeys = { "builderId", "builder", "builder_id", "buildService", "build.service" }; private static readonly string[] SourceRepoKeys = { "sourceRepository", "sourceRepo", "repository", "repo", "gitUrl", "git.url" }; private static readonly string[] SourceCommitKeys = { "sourceCommit", "commit", "gitCommit", "git.commit", "revision" }; public BuildProvenanceChain Build(ParsedSbom sbom) { ArgumentNullException.ThrowIfNull(sbom); var buildInfo = sbom.BuildInfo; var formulation = sbom.Formulation; var environment = buildInfo?.Environment ?? ImmutableDictionary.Empty; var builderId = FindParameter(buildInfo, BuilderIdKeys) ?? buildInfo?.BuildType; var sourceRepo = FindParameter(buildInfo, SourceRepoKeys); var sourceCommit = FindParameter(buildInfo, SourceCommitKeys); var configUri = buildInfo?.ConfigSourceUri ?? buildInfo?.ConfigSourceEntrypoint; var configDigest = buildInfo?.ConfigSourceDigest; var inputs = new HashSet(StringComparer.OrdinalIgnoreCase); var outputs = new HashSet(StringComparer.OrdinalIgnoreCase); if (formulation is not null) { foreach (var component in formulation.Components) { if (!string.IsNullOrWhiteSpace(component.BomRef)) { inputs.Add(component.BomRef!); } foreach (var reference in component.ComponentRefs) { if (!string.IsNullOrWhiteSpace(reference)) { inputs.Add(reference); } } } foreach (var workflow in formulation.Workflows) { foreach (var input in workflow.InputRefs) { if (!string.IsNullOrWhiteSpace(input)) { inputs.Add(input); } } foreach (var output in workflow.OutputRefs) { if (!string.IsNullOrWhiteSpace(output)) { outputs.Add(output); } } } foreach (var task in formulation.Tasks) { foreach (var input in task.InputRefs) { if (!string.IsNullOrWhiteSpace(input)) { inputs.Add(input); } } foreach (var output in task.OutputRefs) { if (!string.IsNullOrWhiteSpace(output)) { outputs.Add(output); } } } } return new BuildProvenanceChain { BuilderId = builderId, SourceRepository = sourceRepo, SourceCommit = sourceCommit, BuildConfigUri = configUri, BuildConfigDigest = configDigest, Environment = environment, Inputs = inputs.Select(reference => new BuildInput { Reference = reference }).ToImmutableArray(), Outputs = outputs.Select(reference => new BuildOutput { Reference = reference }).ToImmutableArray() }; } private static string? FindParameter(ParsedBuildInfo? buildInfo, IEnumerable keys) { if (buildInfo?.Parameters is null || buildInfo.Parameters.IsEmpty) { return null; } foreach (var key in keys) { if (string.IsNullOrWhiteSpace(key)) { continue; } if (buildInfo.Parameters.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value)) { return value.Trim(); } } return null; } }