Refactor code structure for improved readability and maintainability; optimize performance in key functions.
This commit is contained in:
@@ -10,6 +10,9 @@ public sealed class SbomIngestTransformer
|
||||
private const string DependsOnEdgeKind = "DEPENDS_ON";
|
||||
private const string DeclaredInEdgeKind = "DECLARED_IN";
|
||||
private const string BuiltFromEdgeKind = "BUILT_FROM";
|
||||
private const string SbomNodeKind = "sbom";
|
||||
private const string SbomVersionOfEdgeKind = "SBOM_VERSION_OF";
|
||||
private const string SbomLineageEdgePrefix = "SBOM_LINEAGE_";
|
||||
|
||||
public GraphBuildBatch Transform(SbomSnapshot snapshot)
|
||||
{
|
||||
@@ -19,6 +22,7 @@ public sealed class SbomIngestTransformer
|
||||
var edges = new List<JsonObject>();
|
||||
|
||||
var artifactNodes = new Dictionary<string, JsonObject>(StringComparer.OrdinalIgnoreCase);
|
||||
var sbomNodes = new Dictionary<string, JsonObject>(StringComparer.OrdinalIgnoreCase);
|
||||
var componentNodes = new Dictionary<string, JsonObject>(StringComparer.OrdinalIgnoreCase);
|
||||
var fileNodes = new Dictionary<string, JsonObject>(StringComparer.OrdinalIgnoreCase);
|
||||
var licenseCandidates = new Dictionary<(string License, string SourceDigest), LicenseCandidate>(LicenseKeyComparer.Instance);
|
||||
@@ -30,6 +34,16 @@ public sealed class SbomIngestTransformer
|
||||
nodes.Add(artifactNode);
|
||||
artifactNodes[GetArtifactKey(snapshot.ArtifactDigest, snapshot.SbomDigest)] = artifactNode;
|
||||
|
||||
var sbomNode = CreateSbomNode(snapshot);
|
||||
if (sbomNode is not null)
|
||||
{
|
||||
nodes.Add(sbomNode);
|
||||
sbomNodes[snapshot.SbomDigest] = sbomNode;
|
||||
|
||||
var sbomEdge = CreateSbomVersionOfEdge(snapshot, sbomNode, artifactNode, NextEdgeOffset());
|
||||
edges.Add(sbomEdge);
|
||||
}
|
||||
|
||||
foreach (var component in snapshot.Components)
|
||||
{
|
||||
var componentNode = CreateComponentNode(snapshot, component);
|
||||
@@ -91,6 +105,27 @@ public sealed class SbomIngestTransformer
|
||||
edges.Add(edge);
|
||||
}
|
||||
|
||||
if (sbomNode is not null && snapshot.Lineage.Count > 0)
|
||||
{
|
||||
foreach (var lineage in snapshot.Lineage)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(lineage.SbomDigest))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sbomNodes.TryGetValue(lineage.SbomDigest, out var relatedNode))
|
||||
{
|
||||
relatedNode = CreateLineageSbomNode(snapshot, lineage);
|
||||
nodes.Add(relatedNode);
|
||||
sbomNodes[lineage.SbomDigest] = relatedNode;
|
||||
}
|
||||
|
||||
var edge = CreateLineageEdge(snapshot, sbomNode, relatedNode, lineage, NextEdgeOffset());
|
||||
edges.Add(edge);
|
||||
}
|
||||
}
|
||||
|
||||
var orderedNodes = nodes
|
||||
.OrderBy(node => node["kind"]!.GetValue<string>(), StringComparer.Ordinal)
|
||||
.ThenBy(node => node["id"]!.GetValue<string>(), StringComparer.Ordinal)
|
||||
@@ -168,6 +203,76 @@ public sealed class SbomIngestTransformer
|
||||
ValidTo: null));
|
||||
}
|
||||
|
||||
private static JsonObject? CreateSbomNode(SbomSnapshot snapshot)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(snapshot.SbomDigest))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var attributes = new JsonObject
|
||||
{
|
||||
["sbom_digest"] = snapshot.SbomDigest,
|
||||
["artifact_digest"] = snapshot.ArtifactDigest,
|
||||
["format"] = snapshot.SbomFormat,
|
||||
["format_version"] = snapshot.SbomFormatVersion
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.SbomVersionId))
|
||||
{
|
||||
attributes["version_id"] = snapshot.SbomVersionId;
|
||||
}
|
||||
|
||||
if (snapshot.SbomSequence > 0)
|
||||
{
|
||||
attributes["sequence"] = snapshot.SbomSequence;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.ChainId))
|
||||
{
|
||||
attributes["chain_id"] = snapshot.ChainId;
|
||||
}
|
||||
|
||||
return GraphDocumentFactory.CreateNode(new GraphNodeSpec(
|
||||
Tenant: snapshot.Tenant,
|
||||
Kind: SbomNodeKind,
|
||||
CanonicalKey: new Dictionary<string, string>
|
||||
{
|
||||
["tenant"] = snapshot.Tenant,
|
||||
["sbom_digest"] = snapshot.SbomDigest
|
||||
},
|
||||
Attributes: attributes,
|
||||
Provenance: new GraphProvenanceSpec(snapshot.Source, snapshot.CollectedAt, snapshot.SbomDigest, snapshot.EventOffset + 1),
|
||||
ValidFrom: snapshot.CollectedAt,
|
||||
ValidTo: null));
|
||||
}
|
||||
|
||||
private static JsonObject CreateLineageSbomNode(SbomSnapshot snapshot, SbomLineageReference lineage)
|
||||
{
|
||||
var attributes = new JsonObject
|
||||
{
|
||||
["sbom_digest"] = lineage.SbomDigest,
|
||||
["artifact_digest"] = lineage.ArtifactDigest
|
||||
};
|
||||
|
||||
return GraphDocumentFactory.CreateNode(new GraphNodeSpec(
|
||||
Tenant: snapshot.Tenant,
|
||||
Kind: SbomNodeKind,
|
||||
CanonicalKey: new Dictionary<string, string>
|
||||
{
|
||||
["tenant"] = snapshot.Tenant,
|
||||
["sbom_digest"] = lineage.SbomDigest
|
||||
},
|
||||
Attributes: attributes,
|
||||
Provenance: new GraphProvenanceSpec(
|
||||
ResolveSource(lineage.Source, snapshot.Source),
|
||||
lineage.CollectedAt,
|
||||
lineage.SbomDigest,
|
||||
lineage.EventOffset),
|
||||
ValidFrom: lineage.CollectedAt,
|
||||
ValidTo: null));
|
||||
}
|
||||
|
||||
private static JsonObject CreateComponentNode(SbomSnapshot snapshot, SbomComponent component)
|
||||
{
|
||||
var attributes = new JsonObject
|
||||
@@ -199,6 +304,59 @@ public sealed class SbomIngestTransformer
|
||||
ValidTo: null));
|
||||
}
|
||||
|
||||
private static JsonObject CreateSbomVersionOfEdge(SbomSnapshot snapshot, JsonObject sbomNode, JsonObject artifactNode, long eventOffset)
|
||||
{
|
||||
return GraphDocumentFactory.CreateEdge(new GraphEdgeSpec(
|
||||
Tenant: snapshot.Tenant,
|
||||
Kind: SbomVersionOfEdgeKind,
|
||||
CanonicalKey: new Dictionary<string, string>
|
||||
{
|
||||
["tenant"] = snapshot.Tenant,
|
||||
["sbom_node_id"] = sbomNode["id"]!.GetValue<string>(),
|
||||
["artifact_node_id"] = artifactNode["id"]!.GetValue<string>()
|
||||
},
|
||||
Attributes: new JsonObject
|
||||
{
|
||||
["sbom_digest"] = snapshot.SbomDigest,
|
||||
["artifact_digest"] = snapshot.ArtifactDigest,
|
||||
["chain_id"] = snapshot.ChainId,
|
||||
["sequence"] = snapshot.SbomSequence
|
||||
},
|
||||
Provenance: new GraphProvenanceSpec(snapshot.Source, snapshot.CollectedAt, snapshot.SbomDigest, eventOffset),
|
||||
ValidFrom: snapshot.CollectedAt,
|
||||
ValidTo: null));
|
||||
}
|
||||
|
||||
private static JsonObject CreateLineageEdge(SbomSnapshot snapshot, JsonObject fromNode, JsonObject toNode, SbomLineageReference lineage, long fallbackOffset)
|
||||
{
|
||||
var offset = lineage.EventOffset > 0 ? lineage.EventOffset : fallbackOffset;
|
||||
var kind = NormalizeLineageKind(lineage.Relationship);
|
||||
|
||||
return GraphDocumentFactory.CreateEdge(new GraphEdgeSpec(
|
||||
Tenant: snapshot.Tenant,
|
||||
Kind: kind,
|
||||
CanonicalKey: new Dictionary<string, string>
|
||||
{
|
||||
["tenant"] = snapshot.Tenant,
|
||||
["from_sbom_node_id"] = fromNode["id"]!.GetValue<string>(),
|
||||
["to_sbom_node_id"] = toNode["id"]!.GetValue<string>(),
|
||||
["relationship"] = lineage.Relationship
|
||||
},
|
||||
Attributes: new JsonObject
|
||||
{
|
||||
["relationship"] = lineage.Relationship,
|
||||
["sbom_digest"] = lineage.SbomDigest,
|
||||
["artifact_digest"] = lineage.ArtifactDigest
|
||||
},
|
||||
Provenance: new GraphProvenanceSpec(
|
||||
ResolveSource(lineage.Source, snapshot.Source),
|
||||
lineage.CollectedAt,
|
||||
snapshot.SbomDigest,
|
||||
offset),
|
||||
ValidFrom: lineage.CollectedAt,
|
||||
ValidTo: null));
|
||||
}
|
||||
|
||||
private static JsonObject CreateFileNode(SbomSnapshot snapshot, SbomComponentFile file)
|
||||
{
|
||||
var attributes = new JsonObject
|
||||
@@ -390,6 +548,17 @@ public sealed class SbomIngestTransformer
|
||||
return array;
|
||||
}
|
||||
|
||||
private static string NormalizeLineageKind(string relationship)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(relationship))
|
||||
{
|
||||
return SbomLineageEdgePrefix + "UNKNOWN";
|
||||
}
|
||||
|
||||
var normalized = relationship.Trim().Replace('-', '_').Replace(' ', '_').ToUpperInvariant();
|
||||
return SbomLineageEdgePrefix + normalized;
|
||||
}
|
||||
|
||||
private static LicenseCandidate CreateLicenseCandidate(SbomSnapshot snapshot, SbomComponent component)
|
||||
{
|
||||
var collectedAt = component.CollectedAt.AddSeconds(2);
|
||||
|
||||
@@ -16,6 +16,21 @@ public sealed class SbomSnapshot
|
||||
[JsonPropertyName("sbomDigest")]
|
||||
public string SbomDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sbomVersionId")]
|
||||
public string SbomVersionId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sbomSequence")]
|
||||
public int SbomSequence { get; init; }
|
||||
|
||||
[JsonPropertyName("sbomFormat")]
|
||||
public string SbomFormat { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sbomFormatVersion")]
|
||||
public string SbomFormatVersion { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("chainId")]
|
||||
public string ChainId { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("collectedAt")]
|
||||
public DateTimeOffset CollectedAt { get; init; } = DateTimeOffset.UnixEpoch;
|
||||
|
||||
@@ -33,6 +48,9 @@ public sealed class SbomSnapshot
|
||||
|
||||
[JsonPropertyName("baseArtifacts")]
|
||||
public IReadOnlyList<SbomBaseArtifact> BaseArtifacts { get; init; } = Array.Empty<SbomBaseArtifact>();
|
||||
|
||||
[JsonPropertyName("lineage")]
|
||||
public IReadOnlyList<SbomLineageReference> Lineage { get; init; } = Array.Empty<SbomLineageReference>();
|
||||
}
|
||||
|
||||
public sealed class SbomArtifactMetadata
|
||||
@@ -229,3 +247,24 @@ public sealed class SbomBaseArtifact
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class SbomLineageReference
|
||||
{
|
||||
[JsonPropertyName("relationship")]
|
||||
public string Relationship { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("sbomDigest")]
|
||||
public string SbomDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("artifactDigest")]
|
||||
public string ArtifactDigest { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("eventOffset")]
|
||||
public long EventOffset { get; init; }
|
||||
|
||||
[JsonPropertyName("collectedAt")]
|
||||
public DateTimeOffset CollectedAt { get; init; } = DateTimeOffset.UnixEpoch;
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user