Skip to content

Commit

Permalink
SBOM cataloger (#1029)
Browse files Browse the repository at this point in the history
* SBOM cataloger

Signed-off-by: Patrik Beno <[email protected]>

* sbom-cataloger: turn off by default

and add integration test

Signed-off-by: Patrik Beno <[email protected]>

* SBOM cataloger

Signed-off-by: Patrik Beno <[email protected]>

* SBOM cataloger (optimize)

Signed-off-by: Patrik Beno <[email protected]>

* SBOM cataloger (fix)

Signed-off-by: Patrik Beno <[email protected]>

* SBOM cataloger (fix imports #1172)

Signed-off-by: Patrik Beno <[email protected]>

* SBOM cataloger (fix: support group attribute in CDX SBOMs)

Signed-off-by: Patrik Beno <[email protected]>

* port to generic cataloger and add relationship to original file

Signed-off-by: Alex Goodman <[email protected]>

* generalize parser for all format globs

Signed-off-by: Alex Goodman <[email protected]>

Signed-off-by: Patrik Beno <[email protected]>
Signed-off-by: Alex Goodman <[email protected]>
Co-authored-by: Tom Fay <[email protected]>
Co-authored-by: Alex Goodman <[email protected]>
  • Loading branch information
3 people authored Nov 16, 2022
1 parent 0774ad1 commit 0c4b99c
Show file tree
Hide file tree
Showing 17 changed files with 3,855 additions and 109 deletions.
25 changes: 16 additions & 9 deletions cmd/syft/cli/options/format.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
package options

import (
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/formats/cyclonedxjson"
"github.com/anchore/syft/syft/formats/cyclonedxxml"
"github.com/anchore/syft/syft/formats/github"
"github.com/anchore/syft/syft/formats/spdx22json"
"github.com/anchore/syft/syft/formats/spdx22tagvalue"
"github.com/anchore/syft/syft/formats/syftjson"
"github.com/anchore/syft/syft/formats/table"
"github.com/anchore/syft/syft/formats/text"
"github.com/anchore/syft/syft/sbom"
)

func FormatAliases(ids ...sbom.FormatID) (aliases []string) {
for _, id := range ids {
switch id {
case syft.JSONFormatID:
case syftjson.ID:
aliases = append(aliases, "syft-json")
case syft.TextFormatID:
case text.ID:
aliases = append(aliases, "text")
case syft.TableFormatID:
case table.ID:
aliases = append(aliases, "table")
case syft.SPDXJSONFormatID:
case spdx22json.ID:
aliases = append(aliases, "spdx-json")
case syft.SPDXTagValueFormatID:
case spdx22tagvalue.ID:
aliases = append(aliases, "spdx-tag-value")
case syft.CycloneDxXMLFormatID:
case cyclonedxxml.ID:
aliases = append(aliases, "cyclonedx-xml")
case syft.CycloneDxJSONFormatID:
case cyclonedxjson.ID:
aliases = append(aliases, "cyclonedx-json")
case syft.GitHubID:
case github.ID:
aliases = append(aliases, "github", "github-json")
default:
aliases = append(aliases, string(id))
Expand Down
3 changes: 3 additions & 0 deletions syft/artifact/relationship.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const (

// DependencyOfRelationship is a proxy for the SPDX 2.2.1 DEPENDENCY_OF relationship.
DependencyOfRelationship RelationshipType = "dependency-of"

// DescribedByRelationship is a proxy for the SPDX 2.2.2 DESCRIBED_BY relationship.
DescribedByRelationship RelationshipType = "described-by"
)

type RelationshipType string
Expand Down
28 changes: 5 additions & 23 deletions syft/encode_decode.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,18 @@
package syft

import (
"bytes"
"fmt"
"io"

"github.com/anchore/syft/syft/formats"
"github.com/anchore/syft/syft/sbom"
)

// Encode takes all SBOM elements and a format option and encodes an SBOM document.
// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
func Encode(s sbom.SBOM, f sbom.Format) ([]byte, error) {
buff := bytes.Buffer{}

if err := f.Encode(&buff, s); err != nil {
return nil, fmt.Errorf("unable to encode sbom: %w", err)
}

return buff.Bytes(), nil
return formats.Encode(s, f)
}

// Decode takes a reader for an SBOM and generates all internal SBOM elements.
// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
func Decode(reader io.Reader) (*sbom.SBOM, sbom.Format, error) {
by, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("unable to read sbom: %w", err)
}

f := IdentifyFormat(by)
if f == nil {
return nil, nil, fmt.Errorf("unable to identify format")
}

s, err := f.Decode(bytes.NewReader(by))
return s, f, err
return formats.Decode(reader)
}
83 changes: 11 additions & 72 deletions syft/formats.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package syft

import (
"bytes"
"strings"

"github.com/anchore/syft/syft/formats"
"github.com/anchore/syft/syft/formats/cyclonedxjson"
"github.com/anchore/syft/syft/formats/cyclonedxxml"
"github.com/anchore/syft/syft/formats/github"
Expand All @@ -17,94 +15,35 @@ import (
)

// these have been exported for the benefit of API users
// TODO: deprecated: now that the formats package has been moved to syft/formats, will be removed in v1.0.0
const (
JSONFormatID = syftjson.ID
TextFormatID = text.ID
TableFormatID = table.ID
CycloneDxXMLFormatID = cyclonedxxml.ID
CycloneDxJSONFormatID = cyclonedxjson.ID
GitHubID = github.ID
GitHubFormatID = github.ID
SPDXTagValueFormatID = spdx22tagvalue.ID
SPDXJSONFormatID = spdx22json.ID
TemplateFormatID = template.ID
)

var formats []sbom.Format

func init() {
formats = []sbom.Format{
syftjson.Format(),
cyclonedxxml.Format(),
cyclonedxjson.Format(),
github.Format(),
spdx22tagvalue.Format(),
spdx22json.Format(),
table.Format(),
text.Format(),
template.Format(),
}
}

// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
func FormatIDs() (ids []sbom.FormatID) {
for _, f := range formats {
ids = append(ids, f.ID())
}
return ids
return formats.IDs()
}

// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
func FormatByID(id sbom.FormatID) sbom.Format {
for _, f := range formats {
if f.ID() == id {
return f
}
}
return nil
return formats.ByID(id)
}

// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
func FormatByName(name string) sbom.Format {
cleanName := cleanFormatName(name)
for _, f := range formats {
if cleanFormatName(string(f.ID())) == cleanName {
return f
}
}

// handle any aliases for any supported format
switch cleanName {
case "json", "syftjson":
return FormatByID(syftjson.ID)
case "cyclonedx", "cyclone", "cyclonedxxml":
return FormatByID(cyclonedxxml.ID)
case "cyclonedxjson":
return FormatByID(cyclonedxjson.ID)
case "github", "githubjson":
return FormatByID(github.ID)
case "spdx", "spdxtv", "spdxtagvalue":
return FormatByID(spdx22tagvalue.ID)
case "spdxjson":
return FormatByID(spdx22json.ID)
case "table":
return FormatByID(table.ID)
case "text":
return FormatByID(text.ID)
case "template":
FormatByID(template.ID)
}

return nil
}

func cleanFormatName(name string) string {
r := strings.NewReplacer("-", "", "_", "")
return strings.ToLower(r.Replace(name))
return formats.ByName(name)
}

// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
func IdentifyFormat(by []byte) sbom.Format {
for _, f := range formats {
if err := f.Validate(bytes.NewReader(by)); err != nil {
continue
}
return f
}
return nil
return formats.Identify(by)
}
3 changes: 3 additions & 0 deletions syft/formats/common/spdxhelpers/source_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ func SourceInfo(p pkg.Package) string {
default:
answer = "acquired package info from the following paths"
}
if p.FoundBy == "sbom-cataloger" {
answer = "acquired package info from SBOM"
}
var paths []string
for _, l := range p.Locations.ToSlice() {
paths = append(paths, l.RealPath)
Expand Down
124 changes: 124 additions & 0 deletions syft/formats/formats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package formats

import (
"bytes"
"fmt"
"io"
"strings"

"github.com/anchore/syft/syft/formats/cyclonedxjson"
"github.com/anchore/syft/syft/formats/cyclonedxxml"
"github.com/anchore/syft/syft/formats/github"
"github.com/anchore/syft/syft/formats/spdx22json"
"github.com/anchore/syft/syft/formats/spdx22tagvalue"
"github.com/anchore/syft/syft/formats/syftjson"
"github.com/anchore/syft/syft/formats/table"
"github.com/anchore/syft/syft/formats/template"
"github.com/anchore/syft/syft/formats/text"
"github.com/anchore/syft/syft/sbom"
)

func Formats() []sbom.Format {
return []sbom.Format{
syftjson.Format(),
cyclonedxxml.Format(),
cyclonedxjson.Format(),
github.Format(),
spdx22tagvalue.Format(),
spdx22json.Format(),
table.Format(),
text.Format(),
template.Format(),
}
}

func Identify(by []byte) sbom.Format {
for _, f := range Formats() {
if err := f.Validate(bytes.NewReader(by)); err != nil {
continue
}
return f
}
return nil
}

func ByName(name string) sbom.Format {
cleanName := cleanFormatName(name)
for _, f := range Formats() {
if cleanFormatName(string(f.ID())) == cleanName {
return f
}
}

// handle any aliases for any supported format
switch cleanName {
case "json", "syftjson":
return ByID(syftjson.ID)
case "cyclonedx", "cyclone", "cyclonedxxml":
return ByID(cyclonedxxml.ID)
case "cyclonedxjson":
return ByID(cyclonedxjson.ID)
case "github", "githubjson":
return ByID(github.ID)
case "spdx", "spdxtv", "spdxtagvalue":
return ByID(spdx22tagvalue.ID)
case "spdxjson":
return ByID(spdx22json.ID)
case "table":
return ByID(table.ID)
case "text":
return ByID(text.ID)
case "template":
ByID(template.ID)
}

return nil
}

func IDs() (ids []sbom.FormatID) {
for _, f := range Formats() {
ids = append(ids, f.ID())
}
return ids
}

func ByID(id sbom.FormatID) sbom.Format {
for _, f := range Formats() {
if f.ID() == id {
return f
}
}
return nil
}

func cleanFormatName(name string) string {
r := strings.NewReplacer("-", "", "_", "")
return strings.ToLower(r.Replace(name))
}

// Encode takes all SBOM elements and a format option and encodes an SBOM document.
func Encode(s sbom.SBOM, f sbom.Format) ([]byte, error) {
buff := bytes.Buffer{}

if err := f.Encode(&buff, s); err != nil {
return nil, fmt.Errorf("unable to encode sbom: %w", err)
}

return buff.Bytes(), nil
}

// Decode takes a reader for an SBOM and generates all internal SBOM elements.
func Decode(reader io.Reader) (*sbom.SBOM, sbom.Format, error) {
by, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("unable to read sbom: %w", err)
}

f := Identify(by)
if f == nil {
return nil, nil, fmt.Errorf("unable to identify format")
}

s, err := f.Decode(bytes.NewReader(by))
return s, f, err
}
10 changes: 5 additions & 5 deletions syft/formats_test.go → syft/formats/formats_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package syft
package formats

import (
"bytes"
Expand Down Expand Up @@ -37,15 +37,15 @@ func TestIdentify(t *testing.T) {
assert.NoError(t, err)
by, err := io.ReadAll(f)
assert.NoError(t, err)
frmt := IdentifyFormat(by)
frmt := Identify(by)
assert.NotNil(t, frmt)
assert.Equal(t, test.expected, frmt.ID())
})
}
}

func TestFormats_EmptyInput(t *testing.T) {
for _, format := range formats {
for _, format := range Formats() {
t.Run(format.ID().String(), func(t *testing.T) {
t.Run("format.Decode", func(t *testing.T) {
input := bytes.NewReader(nil)
Expand All @@ -69,7 +69,7 @@ func TestFormats_EmptyInput(t *testing.T) {
}
}

func TestFormatByName(t *testing.T) {
func TestByName(t *testing.T) {

tests := []struct {
name string
Expand Down Expand Up @@ -190,7 +190,7 @@ func TestFormatByName(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := FormatByName(tt.name)
f := ByName(tt.name)
if tt.want == "" {
require.Nil(t, f)
return
Expand Down
File renamed without changes.
Loading

0 comments on commit 0c4b99c

Please sign in to comment.