From 732b95a8cbaa807eea94bafa6ddd5f782922ce46 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Fri, 3 Apr 2026 08:35:53 +0530 Subject: [PATCH 1/4] feat(materials): extract main component info from SPDX files Follow the CycloneDX pattern to extract and populate MainComponent in SBOMArtifact for SPDX JSON materials. Uses spdxlib.GetDescribedPackageIDs to find the described package, then extracts name, version, and kind (PrimaryPackagePurpose). Container names are standardized via go-containerregistry. Gracefully skips when no described package is found. Fixes #2580 Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Vibhav Bobade --- pkg/attestation/crafter/materials/spdxjson.go | 88 ++++++++++++++++++- .../crafter/materials/spdxjson_test.go | 88 +++++++++++++++---- .../testdata/sbom-spdx-container.json | 49 +++++++++++ .../testdata/sbom-spdx-no-purpose.json | 34 +++++++ .../sbom-spdx-with-described-package.json | 49 +++++++++++ 5 files changed, 288 insertions(+), 20 deletions(-) create mode 100644 pkg/attestation/crafter/materials/testdata/sbom-spdx-container.json create mode 100644 pkg/attestation/crafter/materials/testdata/sbom-spdx-no-purpose.json create mode 100644 pkg/attestation/crafter/materials/testdata/sbom-spdx-with-described-package.json diff --git a/pkg/attestation/crafter/materials/spdxjson.go b/pkg/attestation/crafter/materials/spdxjson.go index 885ad2cdd..d02b25999 100644 --- a/pkg/attestation/crafter/materials/spdxjson.go +++ b/pkg/attestation/crafter/materials/spdxjson.go @@ -24,10 +24,11 @@ import ( schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" "github.com/chainloop-dev/chainloop/pkg/casclient" + remotename "github.com/google/go-containerregistry/pkg/name" + "github.com/rs/zerolog" "github.com/spdx/tools-golang/json" "github.com/spdx/tools-golang/spdx" - - "github.com/rs/zerolog" + "github.com/spdx/tools-golang/spdxlib" ) type SPDXJSONCrafter struct { @@ -65,13 +66,92 @@ func (i *SPDXJSONCrafter) Craft(ctx context.Context, filePath string) (*api.Atte return nil, err } + res := m + res.M = &api.Attestation_Material_SbomArtifact{ + SbomArtifact: &api.Attestation_Material_SBOMArtifact{ + Artifact: m.GetArtifact(), + }, + } + + // Extract main component information from SPDX document + if err := i.extractMainComponent(m, doc); err != nil { + i.logger.Debug().Err(err).Msg("error extracting main component from spdx sbom, skipping...") + } + i.injectAnnotations(m, doc) - return m, nil + return res, nil +} + +// extractMainComponent inspects the SPDX document and extracts the main component if any. +// It uses the first described package (via DESCRIBES relationship). If multiple described +// packages exist, only the first is used and a warning is logged. +// NOTE: SPDX PrimaryPackagePurpose values (APPLICATION, CONTAINER, FRAMEWORK, LIBRARY, etc.) +// are lowercased for consistency with CycloneDX component types. The two specs have different +// vocabularies so consumers should handle both sets of values. +func (i *SPDXJSONCrafter) extractMainComponent(m *api.Attestation_Material, doc *spdx.Document) error { + describedIDs, err := spdxlib.GetDescribedPackageIDs(doc) + if err != nil { + return fmt.Errorf("couldn't get described packages: %w", err) + } + + if len(describedIDs) == 0 { + return fmt.Errorf("no described packages found") + } + + if len(describedIDs) > 1 { + i.logger.Warn().Int("count", len(describedIDs)).Msg("multiple described packages found, using the first one") + } + + // Use the first described package + targetID := describedIDs[0] + + // Find the package by ID + var describedPkg *spdx.Package + for _, pkg := range doc.Packages { + if pkg.PackageSPDXIdentifier == targetID { + describedPkg = pkg + break + } + } + + if describedPkg == nil { + return fmt.Errorf("described package %q not found in packages list", targetID) + } + + name := describedPkg.PackageName + version := describedPkg.PackageVersion + + // PrimaryPackagePurpose is optional in SPDX 2.3. If absent, skip main component extraction + // since we cannot determine the component kind. + kind := strings.ToLower(describedPkg.PrimaryPackagePurpose) + if kind == "" { + return fmt.Errorf("described package %q has no PrimaryPackagePurpose set", describedPkg.PackageName) + } + + // For container packages, standardize the name via go-containerregistry + // to get the full repository name and strip any tag (matching CycloneDX behavior) + if kind == containerComponentKind { + ref, err := remotename.ParseReference(name) + if err != nil { + return fmt.Errorf("couldn't parse OCI image repository name: %w", err) + } + name = ref.Context().String() + } + + m.M.(*api.Attestation_Material_SbomArtifact).SbomArtifact.MainComponent = &api.Attestation_Material_SBOMArtifact_MainComponent{ + Name: name, + Kind: kind, + Version: version, + } + + return nil } func (i *SPDXJSONCrafter) injectAnnotations(m *api.Attestation_Material, doc *spdx.Document) { - m.Annotations = make(map[string]string) + if m.Annotations == nil { + m.Annotations = make(map[string]string) + } // Extract all tools from the creators array var tools []Tool diff --git a/pkg/attestation/crafter/materials/spdxjson_test.go b/pkg/attestation/crafter/materials/spdxjson_test.go index 3b82cc763..50022fbf8 100644 --- a/pkg/attestation/crafter/materials/spdxjson_test.go +++ b/pkg/attestation/crafter/materials/spdxjson_test.go @@ -21,7 +21,6 @@ import ( "testing" contractAPI "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" - attestationApi "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials" "github.com/chainloop-dev/chainloop/pkg/casclient" mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" @@ -66,13 +65,17 @@ func TestNewSPDXJSONCrafter(t *testing.T) { func TestSPDXJSONCraft(t *testing.T) { testCases := []struct { - name string - filePath string - wantErr string - wantDigest string - wantFilename string - annotations map[string]string - absentAnnotations []string + name string + filePath string + wantErr string + wantDigest string + wantFilename string + wantMainComponent string + wantMainComponentKind string + wantMainComponentVersion string + wantNoMainComponent bool + annotations map[string]string + absentAnnotations []string }{ { name: "invalid sbom format", @@ -90,10 +93,11 @@ func TestSPDXJSONCraft(t *testing.T) { wantErr: "unexpected material type", }, { - name: "valid artifact type", - filePath: "./testdata/sbom-spdx.json", - wantDigest: "sha256:fe2636fb6c698a29a315278b762b2000efd5959afe776ee4f79f1ed523365a33", - wantFilename: "sbom-spdx.json", + name: "valid artifact type (no described package)", + filePath: "./testdata/sbom-spdx.json", + wantDigest: "sha256:fe2636fb6c698a29a315278b762b2000efd5959afe776ee4f79f1ed523365a33", + wantFilename: "sbom-spdx.json", + wantNoMainComponent: true, annotations: map[string]string{ "chainloop.material.tool.name": "syft", "chainloop.material.tool.version": "0.73.0", @@ -132,6 +136,46 @@ func TestSPDXJSONCraft(t *testing.T) { "chainloop.material.tools": `["spdxgen@1.0.0","scanner@2.1.5"]`, }, }, + { + name: "with described application package", + filePath: "./testdata/sbom-spdx-with-described-package.json", + wantDigest: "sha256:c6aaa874345f9d309f1b7a3e1cdb00817c91326fcc8ede9507dce882b0efdf16", + wantFilename: "sbom-spdx-with-described-package.json", + wantMainComponent: "my-app", + wantMainComponentKind: "application", + wantMainComponentVersion: "1.2.3", + annotations: map[string]string{ + "chainloop.material.tool.name": "syft", + "chainloop.material.tool.version": "0.100.0", + "chainloop.material.tools": `["syft@0.100.0"]`, + }, + }, + { + name: "with described container package", + filePath: "./testdata/sbom-spdx-container.json", + wantDigest: "sha256:47df762774170904a7ab6bf0c565a2c525b60c3f92f9484744f9aafc631ce307", + wantFilename: "sbom-spdx-container.json", + wantMainComponent: "ghcr.io/chainloop-dev/chainloop/control-plane", + wantMainComponentKind: "container", + wantMainComponentVersion: "sha256:abcdef1234567890", + annotations: map[string]string{ + "chainloop.material.tool.name": "trivy", + "chainloop.material.tool.version": "0.50.0", + "chainloop.material.tools": `["trivy@0.50.0"]`, + }, + }, + { + name: "described package without PrimaryPackagePurpose (optional in SPDX 2.3)", + filePath: "./testdata/sbom-spdx-no-purpose.json", + wantDigest: "sha256:140b55bcbdd447fee1c86d50d8459b05159bebcd80a8a8da4ea6475eeab2f487", + wantFilename: "sbom-spdx-no-purpose.json", + wantNoMainComponent: true, + annotations: map[string]string{ + "chainloop.material.tool.name": "syft", + "chainloop.material.tool.version": "0.100.0", + "chainloop.material.tools": `["syft@0.100.0"]`, + }, + }, } assert := assert.New(t) @@ -166,10 +210,22 @@ func TestSPDXJSONCraft(t *testing.T) { assert.Equal(contractAPI.CraftingSchema_Material_SBOM_SPDX_JSON.String(), got.MaterialType.String()) assert.True(got.UploadedToCas) - // The result includes the digest reference - assert.Equal(got.GetArtifact(), &attestationApi.Attestation_Material_Artifact{ - Id: "test", Digest: tc.wantDigest, Name: tc.wantFilename, - }) + // The result wraps the artifact in SbomArtifact + sbomArtifact := got.GetSbomArtifact() + require.NotNil(t, sbomArtifact) + assert.Equal(tc.wantDigest, sbomArtifact.Artifact.Digest) + assert.Equal(tc.wantFilename, sbomArtifact.Artifact.Name) + assert.Equal("test", sbomArtifact.Artifact.Id) + + // Validate main component extraction + if tc.wantNoMainComponent { + assert.Nil(sbomArtifact.MainComponent) + } else if tc.wantMainComponent != "" { + require.NotNil(t, sbomArtifact.MainComponent) + assert.Equal(tc.wantMainComponent, sbomArtifact.MainComponent.Name) + assert.Equal(tc.wantMainComponentKind, sbomArtifact.MainComponent.Kind) + assert.Equal(tc.wantMainComponentVersion, sbomArtifact.MainComponent.Version) + } // Validate annotations if specified if tc.annotations != nil { diff --git a/pkg/attestation/crafter/materials/testdata/sbom-spdx-container.json b/pkg/attestation/crafter/materials/testdata/sbom-spdx-container.json new file mode 100644 index 000000000..b45d5b162 --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/sbom-spdx-container.json @@ -0,0 +1,49 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "ghcr.io/chainloop-dev/chainloop/control-plane", + "documentNamespace": "https://example.com/test/control-plane-5678", + "creationInfo": { + "licenseListVersion": "3.20", + "creators": [ + "Organization: Example, Inc", + "Tool: trivy-0.50.0" + ], + "created": "2024-01-15T10:00:00Z" + }, + "packages": [ + { + "name": "ghcr.io/chainloop-dev/chainloop/control-plane:v0.55.0", + "SPDXID": "SPDXRef-Package-control-plane", + "versionInfo": "sha256:abcdef1234567890", + "downloadLocation": "NOASSERTION", + "primaryPackagePurpose": "CONTAINER", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "supplier": "Organization: Chainloop" + }, + { + "name": "libc", + "SPDXID": "SPDXRef-Package-libc", + "versionInfo": "2.31", + "downloadLocation": "NOASSERTION", + "licenseConcluded": "GPL-2.0-only", + "licenseDeclared": "GPL-2.0-only", + "copyrightText": "NOASSERTION" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-Package-control-plane", + "relationshipType": "DESCRIBES" + }, + { + "spdxElementId": "SPDXRef-Package-control-plane", + "relatedSpdxElement": "SPDXRef-Package-libc", + "relationshipType": "CONTAINS" + } + ] +} diff --git a/pkg/attestation/crafter/materials/testdata/sbom-spdx-no-purpose.json b/pkg/attestation/crafter/materials/testdata/sbom-spdx-no-purpose.json new file mode 100644 index 000000000..04da6f855 --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/sbom-spdx-no-purpose.json @@ -0,0 +1,34 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "my-lib", + "documentNamespace": "https://example.com/test/my-lib-9999", + "creationInfo": { + "licenseListVersion": "3.20", + "creators": [ + "Organization: Example, Inc", + "Tool: syft-0.100.0" + ], + "created": "2024-01-15T10:00:00Z" + }, + "packages": [ + { + "name": "my-lib", + "SPDXID": "SPDXRef-Package-my-lib", + "versionInfo": "2.0.0", + "downloadLocation": "https://example.com/my-lib", + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "copyrightText": "NOASSERTION", + "supplier": "Organization: Example, Inc" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-Package-my-lib", + "relationshipType": "DESCRIBES" + } + ] +} diff --git a/pkg/attestation/crafter/materials/testdata/sbom-spdx-with-described-package.json b/pkg/attestation/crafter/materials/testdata/sbom-spdx-with-described-package.json new file mode 100644 index 000000000..a1488b911 --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/sbom-spdx-with-described-package.json @@ -0,0 +1,49 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "my-app", + "documentNamespace": "https://example.com/test/my-app-1234", + "creationInfo": { + "licenseListVersion": "3.20", + "creators": [ + "Organization: Example, Inc", + "Tool: syft-0.100.0" + ], + "created": "2024-01-15T10:00:00Z" + }, + "packages": [ + { + "name": "my-app", + "SPDXID": "SPDXRef-Package-my-app", + "versionInfo": "1.2.3", + "downloadLocation": "https://example.com/my-app", + "primaryPackagePurpose": "APPLICATION", + "licenseConcluded": "Apache-2.0", + "licenseDeclared": "Apache-2.0", + "copyrightText": "NOASSERTION", + "supplier": "Organization: Example, Inc" + }, + { + "name": "dep-a", + "SPDXID": "SPDXRef-Package-dep-a", + "versionInfo": "0.1.0", + "downloadLocation": "NOASSERTION", + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "copyrightText": "NOASSERTION" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-Package-my-app", + "relationshipType": "DESCRIBES" + }, + { + "spdxElementId": "SPDXRef-Package-my-app", + "relatedSpdxElement": "SPDXRef-Package-dep-a", + "relationshipType": "DEPENDS_ON" + } + ] +} From 9e0f4dc2b045bd419d0752f9feb7563a674a89c4 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Tue, 7 Apr 2026 13:46:11 +0530 Subject: [PATCH 2/4] fix(materials): remove assertion on deprecated Artifact.Id field The Id field is deprecated in crafting_state.proto (kept only for server-side compatibility). Remove the test assertion to fix the SA1019 staticcheck lint failure. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Vibhav Bobade --- pkg/attestation/crafter/materials/spdxjson_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/attestation/crafter/materials/spdxjson_test.go b/pkg/attestation/crafter/materials/spdxjson_test.go index 50022fbf8..801985775 100644 --- a/pkg/attestation/crafter/materials/spdxjson_test.go +++ b/pkg/attestation/crafter/materials/spdxjson_test.go @@ -215,7 +215,6 @@ func TestSPDXJSONCraft(t *testing.T) { require.NotNil(t, sbomArtifact) assert.Equal(tc.wantDigest, sbomArtifact.Artifact.Digest) assert.Equal(tc.wantFilename, sbomArtifact.Artifact.Name) - assert.Equal("test", sbomArtifact.Artifact.Id) // Validate main component extraction if tc.wantNoMainComponent { From 1baeeee670ade221dacbcf5d4527f3f4e3c3e683 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Thu, 9 Apr 2026 04:13:04 +0530 Subject: [PATCH 3/4] fix(materials): best-effort SPDX main component extraction Address review feedback: - Return name and version even when PrimaryPackagePurpose is absent (kind will be empty string instead of erroring out) - When OCI image reference parsing fails (e.g. missing registry credentials), log at debug level and continue with the original name instead of returning an error Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Vibhav Bobade --- pkg/attestation/crafter/materials/spdxjson.go | 15 +++++++-------- .../crafter/materials/spdxjson_test.go | 12 +++++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/pkg/attestation/crafter/materials/spdxjson.go b/pkg/attestation/crafter/materials/spdxjson.go index d02b25999..ae5c4b9f9 100644 --- a/pkg/attestation/crafter/materials/spdxjson.go +++ b/pkg/attestation/crafter/materials/spdxjson.go @@ -122,21 +122,20 @@ func (i *SPDXJSONCrafter) extractMainComponent(m *api.Attestation_Material, doc name := describedPkg.PackageName version := describedPkg.PackageVersion - // PrimaryPackagePurpose is optional in SPDX 2.3. If absent, skip main component extraction - // since we cannot determine the component kind. + // PrimaryPackagePurpose is optional in SPDX 2.3. Best effort: return name + // and version even if kind is unknown. kind := strings.ToLower(describedPkg.PrimaryPackagePurpose) - if kind == "" { - return fmt.Errorf("described package %q has no PrimaryPackagePurpose set", describedPkg.PackageName) - } // For container packages, standardize the name via go-containerregistry - // to get the full repository name and strip any tag (matching CycloneDX behavior) + // to get the full repository name and strip any tag (matching CycloneDX behavior). + // If parsing fails (e.g. missing registry credentials), continue with the original name. if kind == containerComponentKind { ref, err := remotename.ParseReference(name) if err != nil { - return fmt.Errorf("couldn't parse OCI image repository name: %w", err) + i.logger.Debug().Err(err).Str("name", name).Msg("couldn't parse OCI image reference, using original name") + } else { + name = ref.Context().String() } - name = ref.Context().String() } m.M.(*api.Attestation_Material_SbomArtifact).SbomArtifact.MainComponent = &api.Attestation_Material_SBOMArtifact_MainComponent{ diff --git a/pkg/attestation/crafter/materials/spdxjson_test.go b/pkg/attestation/crafter/materials/spdxjson_test.go index 801985775..fc8a9966e 100644 --- a/pkg/attestation/crafter/materials/spdxjson_test.go +++ b/pkg/attestation/crafter/materials/spdxjson_test.go @@ -165,11 +165,13 @@ func TestSPDXJSONCraft(t *testing.T) { }, }, { - name: "described package without PrimaryPackagePurpose (optional in SPDX 2.3)", - filePath: "./testdata/sbom-spdx-no-purpose.json", - wantDigest: "sha256:140b55bcbdd447fee1c86d50d8459b05159bebcd80a8a8da4ea6475eeab2f487", - wantFilename: "sbom-spdx-no-purpose.json", - wantNoMainComponent: true, + name: "described package without PrimaryPackagePurpose returns best-effort name and version", + filePath: "./testdata/sbom-spdx-no-purpose.json", + wantDigest: "sha256:140b55bcbdd447fee1c86d50d8459b05159bebcd80a8a8da4ea6475eeab2f487", + wantFilename: "sbom-spdx-no-purpose.json", + wantMainComponent: "my-lib", + wantMainComponentKind: "", + wantMainComponentVersion: "2.0.0", annotations: map[string]string{ "chainloop.material.tool.name": "syft", "chainloop.material.tool.version": "0.100.0", From c881d274a5be0db3f522a3d83e565cc9cb0f1f7b Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Sat, 11 Apr 2026 02:51:07 +0530 Subject: [PATCH 4/4] refactor(materials): extract shared setMainComponent helper Replace SBOMMainComponentInfo struct with a shared setMainComponent() function used by both CycloneDX and SPDX crafters, eliminating duplicated type-assertion logic. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Vibhav Bobade --- .../crafter/materials/cyclonedxjson.go | 25 +++---------------- .../crafter/materials/materials.go | 15 ++++++----- pkg/attestation/crafter/materials/spdxjson.go | 6 +---- 3 files changed, 12 insertions(+), 34 deletions(-) diff --git a/pkg/attestation/crafter/materials/cyclonedxjson.go b/pkg/attestation/crafter/materials/cyclonedxjson.go index 17fbc38d7..a0364ffd7 100644 --- a/pkg/attestation/crafter/materials/cyclonedxjson.go +++ b/pkg/attestation/crafter/materials/cyclonedxjson.go @@ -251,8 +251,6 @@ func (i *CyclonedxJSONCrafter) extractMetadata(m *api.Attestation_Material, meta // extractMainComponent inspects the SBOM and extracts the main component if any and available func (i *CyclonedxJSONCrafter) extractMainComponent(m *api.Attestation_Material, component *cyclonedxComponent) error { - var mainComponent *SBOMMainComponentInfo - // If the version is empty, try to extract it from the properties if component.Version == "" { for _, prop := range component.Properties { @@ -265,33 +263,18 @@ func (i *CyclonedxJSONCrafter) extractMainComponent(m *api.Attestation_Material, } } - if component.Type != containerComponentKind { - mainComponent = &SBOMMainComponentInfo{ - name: component.Name, - kind: component.Type, - version: component.Version, - } - } else { + name := component.Name + if component.Type == containerComponentKind { // Standardize the name to have the full repository name including the registry and // sanitize the name to remove the possible tag from the repository name ref, err := remotename.ParseReference(component.Name) if err != nil { return fmt.Errorf("couldn't parse OCI image repository name: %w", err) } - - mainComponent = &SBOMMainComponentInfo{ - name: ref.Context().String(), - kind: component.Type, - version: component.Version, - } + name = ref.Context().String() } - // If the main component is available, include it in the material - m.M.(*api.Attestation_Material_SbomArtifact).SbomArtifact.MainComponent = &api.Attestation_Material_SBOMArtifact_MainComponent{ - Name: mainComponent.name, - Kind: mainComponent.kind, - Version: mainComponent.version, - } + setMainComponent(m, name, component.Type, component.Version) return nil } diff --git a/pkg/attestation/crafter/materials/materials.go b/pkg/attestation/crafter/materials/materials.go index 6842efffd..320af3bba 100644 --- a/pkg/attestation/crafter/materials/materials.go +++ b/pkg/attestation/crafter/materials/materials.go @@ -84,14 +84,13 @@ var ( ErrBaseUploadAndCraft = errors.New("upload and craft error") ) -// SBOMMainComponentInfo is a struct that holds the main component information for a SBOM -type SBOMMainComponentInfo struct { - // name of the main component the SBOM is about - name string - // kind specifies the type of component - kind string - // version of the component - version string +// setMainComponent sets the main component information on an SBOM material. +// Shared by CycloneDX and SPDX crafters. +func setMainComponent(m *api.Attestation_Material, name, kind, version string) { + m.M.(*api.Attestation_Material_SbomArtifact).SbomArtifact.MainComponent = + &api.Attestation_Material_SBOMArtifact_MainComponent{ + Name: name, Kind: kind, Version: version, + } } type crafterCommon struct { diff --git a/pkg/attestation/crafter/materials/spdxjson.go b/pkg/attestation/crafter/materials/spdxjson.go index ae5c4b9f9..4533ad009 100644 --- a/pkg/attestation/crafter/materials/spdxjson.go +++ b/pkg/attestation/crafter/materials/spdxjson.go @@ -138,11 +138,7 @@ func (i *SPDXJSONCrafter) extractMainComponent(m *api.Attestation_Material, doc } } - m.M.(*api.Attestation_Material_SbomArtifact).SbomArtifact.MainComponent = &api.Attestation_Material_SBOMArtifact_MainComponent{ - Name: name, - Kind: kind, - Version: version, - } + setMainComponent(m, name, kind, version) return nil }