Skip to content

Commit

Permalink
feat: Support SPDX SBOMs (#1498)
Browse files Browse the repository at this point in the history
* feat: use an interface for SBOM model

In preparation for SPDX support, wrap the SbomCyclonedx model in a
generic Sbom interface.

Signed-off-by: Adam Cmiel <[email protected]>

* feat: support SPDX SBOMs

Support unmarshalling the show-sbom task output into either CycloneDX or
SPDX models. Do similar assertions for both SBOM formats.

Signed-off-by: Adam Cmiel <[email protected]>

---------

Signed-off-by: Adam Cmiel <[email protected]>
  • Loading branch information
chmeliik authored Jan 29, 2025
1 parent 118ccac commit 698910d
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 11 deletions.
110 changes: 104 additions & 6 deletions pkg/utils/build/sbom.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,113 @@
package build

import (
"encoding/json"
"fmt"
)

type Sbom interface {
GetPackages() []SbomPackage
}

type SbomPackage interface {
GetName() string
GetVersion() string
GetPurl() string
}

type SbomCyclonedx struct {
BomFormat string
SpecVersion string
Version int
Components []struct {
Name string `json:"name"`
Purl string `json:"purl"`
Type string `json:"type"`
Version string `json:"version"`
} `json:"components"`
Components []CyclonedxComponent `json:"components"`
}

type CyclonedxComponent struct {
Name string `json:"name"`
Purl string `json:"purl"`
Type string `json:"type"`
Version string `json:"version"`
}

func (s *SbomCyclonedx) GetPackages() []SbomPackage {
packages := []SbomPackage{}
for i := range s.Components {
packages = append(packages, &s.Components[i])
}
return packages
}

func (c *CyclonedxComponent) GetName() string {
return c.Name
}

func (c *CyclonedxComponent) GetVersion() string {
return c.Version
}

func (c *CyclonedxComponent) GetPurl() string {
return c.Purl
}

type SbomSpdx struct {
SPDXID string `json:"SPDXID"`
SpdxVersion string `json:"spdxVersion"`
Packages []SpdxPackage `json:"packages"`
}

type SpdxPackage struct {
Name string `json:"name"`
VersionInfo string `json:"versionInfo"`
ExternalRefs []SpdxExternalRef `json:"externalRefs"`
}

type SpdxExternalRef struct {
ReferenceCategory string `json:"referenceCategory"`
ReferenceLocator string `json:"referenceLocator"`
ReferenceType string `json:"referenceType"`
}

func (s *SbomSpdx) GetPackages() []SbomPackage {
packages := []SbomPackage{}
for i := range s.Packages {
packages = append(packages, &s.Packages[i])
}
return packages
}

func (p *SpdxPackage) GetName() string {
return p.Name
}

func (p *SpdxPackage) GetVersion() string {
return p.VersionInfo
}

func (p *SpdxPackage) GetPurl() string {
for _, ref := range p.ExternalRefs {
if ref.ReferenceType == "purl" {
return ref.ReferenceLocator
}
}
return ""
}

func UnmarshalSbom(data []byte) (Sbom, error) {
cdx := SbomCyclonedx{}
if err := json.Unmarshal(data, &cdx); err != nil {
return nil, fmt.Errorf("unmarshalling SBOM: %w", err)
}
if cdx.BomFormat != "" {
return &cdx, nil
}

spdx := SbomSpdx{}
if err := json.Unmarshal(data, &spdx); err != nil {
return nil, fmt.Errorf("unmarshalling SBOM: %w", err)
}
if spdx.SPDXID != "" {
return &spdx, nil
}

return nil, fmt.Errorf("unmarshalling SBOM: doesn't look like either CycloneDX or SPDX")
}
19 changes: 14 additions & 5 deletions tests/build/build_templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,13 +354,22 @@ var _ = framework.BuildSuiteDescribe("Build templates E2E test", Label("build",
sbomTaskLog = log
}

sbom := &build.SbomCyclonedx{}
err = json.Unmarshal([]byte(sbomTaskLog), sbom)
sbom, err := build.UnmarshalSbom([]byte(sbomTaskLog))
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("failed to parse SBOM from show-sbom task output from %s/%s PipelineRun", pr.GetNamespace(), pr.GetName()))
Expect(sbom.BomFormat).ToNot(BeEmpty())
Expect(sbom.SpecVersion).ToNot(BeEmpty())

switch s := sbom.(type) {
case *build.SbomCyclonedx:
Expect(s.BomFormat).ToNot(BeEmpty())
Expect(s.SpecVersion).ToNot(BeEmpty())
case *build.SbomSpdx:
Expect(s.SPDXID).ToNot(BeEmpty())
Expect(s.SpdxVersion).ToNot(BeEmpty())
default:
Fail(fmt.Sprintf("unknown SBOM type: %T", s))
}

if !strings.Contains(scenario.GitURL, "from-scratch") {
Expect(sbom.Components).ToNot(BeEmpty())
Expect(sbom.GetPackages()).ToNot(BeEmpty())
}
})

Expand Down

0 comments on commit 698910d

Please sign in to comment.