Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SBOM cataloger #1029

Merged
merged 9 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading