Refactor source API (#1846)

* refactor source API and syft json source block

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update source detection and format test utils

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* generate list of all source metadata types

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* extract base and root normalization into helper functions

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* preserve syftjson model package name import ref

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* alias should not be a pointer

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman
2023-06-30 14:19:16 +00:00
committed by GitHub
parent 608dbded06
commit 4da3be864f
126 changed files with 7384 additions and 3190 deletions
@@ -4,16 +4,18 @@ import (
"github.com/anchore/syft/syft/source"
)
func DocumentName(srcMetadata source.Metadata) string {
func DocumentName(srcMetadata source.Description) string {
if srcMetadata.Name != "" {
return srcMetadata.Name
}
switch srcMetadata.Scheme {
case source.ImageScheme:
return srcMetadata.ImageMetadata.UserInput
case source.DirectoryScheme, source.FileScheme:
return srcMetadata.Path
switch metadata := srcMetadata.Metadata.(type) {
case source.StereoscopeImageSourceMetadata:
return metadata.UserInput
case source.DirectorySourceMetadata:
return metadata.Path
case source.FileSourceMetadata:
return metadata.Path
default:
return "unknown"
}
@@ -5,31 +5,27 @@ import (
"strings"
"testing"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/internal/sourcemetadata"
"github.com/anchore/syft/syft/source"
)
func Test_DocumentName(t *testing.T) {
allSchemes := strset.New()
for _, s := range source.AllSchemes {
allSchemes.Add(string(s))
}
testedSchemes := strset.New()
tracker := sourcemetadata.NewCompletionTester(t)
tests := []struct {
name string
inputName string
srcMetadata source.Metadata
srcMetadata source.Description
expected string
}{
{
name: "image",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.ImageScheme,
ImageMetadata: source.ImageMetadata{
srcMetadata: source.Description{
Metadata: source.StereoscopeImageSourceMetadata{
UserInput: "image-repo/name:tag",
ID: "id",
ManifestDigest: "digest",
@@ -40,18 +36,16 @@ func Test_DocumentName(t *testing.T) {
{
name: "directory",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.DirectoryScheme,
Path: "some/path/to/place",
srcMetadata: source.Description{
Metadata: source.DirectorySourceMetadata{Path: "some/path/to/place"},
},
expected: "some/path/to/place",
},
{
name: "file",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.FileScheme,
Path: "some/path/to/place",
srcMetadata: source.Description{
Metadata: source.FileSourceMetadata{Path: "some/path/to/place"},
},
expected: "some/path/to/place",
},
@@ -62,10 +56,7 @@ func Test_DocumentName(t *testing.T) {
assert.True(t, strings.HasPrefix(actual, test.expected), fmt.Sprintf("actual name %q", actual))
// track each scheme tested (passed or not)
testedSchemes.Add(string(test.srcMetadata.Scheme))
tracker.Tested(t, test.srcMetadata.Metadata)
})
}
// assert all possible schemes were under test
assert.ElementsMatch(t, allSchemes.List(), testedSchemes.List(), "not all source.Schemes are under test")
}
@@ -18,20 +18,20 @@ const (
inputFile = "file"
)
func DocumentNameAndNamespace(srcMetadata source.Metadata) (string, string) {
name := DocumentName(srcMetadata)
return name, DocumentNamespace(name, srcMetadata)
func DocumentNameAndNamespace(src source.Description) (string, string) {
name := DocumentName(src)
return name, DocumentNamespace(name, src)
}
func DocumentNamespace(name string, srcMetadata source.Metadata) string {
func DocumentNamespace(name string, src source.Description) string {
name = cleanName(name)
input := "unknown-source-type"
switch srcMetadata.Scheme {
case source.ImageScheme:
switch src.Metadata.(type) {
case source.StereoscopeImageSourceMetadata:
input = inputImage
case source.DirectoryScheme:
case source.DirectorySourceMetadata:
input = inputDirectory
case source.FileScheme:
case source.FileSourceMetadata:
input = inputFile
}
@@ -5,31 +5,26 @@ import (
"strings"
"testing"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/internal/sourcemetadata"
"github.com/anchore/syft/syft/source"
)
func Test_documentNamespace(t *testing.T) {
allSchemes := strset.New()
for _, s := range source.AllSchemes {
allSchemes.Add(string(s))
}
testedSchemes := strset.New()
tracker := sourcemetadata.NewCompletionTester(t)
tests := []struct {
name string
inputName string
srcMetadata source.Metadata
expected string
name string
inputName string
src source.Description
expected string
}{
{
name: "image",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.ImageScheme,
ImageMetadata: source.ImageMetadata{
src: source.Description{
Metadata: source.StereoscopeImageSourceMetadata{
UserInput: "image-repo/name:tag",
ID: "id",
ManifestDigest: "digest",
@@ -40,33 +35,32 @@ func Test_documentNamespace(t *testing.T) {
{
name: "directory",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.DirectoryScheme,
Path: "some/path/to/place",
src: source.Description{
Metadata: source.DirectorySourceMetadata{
Path: "some/path/to/place",
},
},
expected: "https://anchore.com/syft/dir/my-name-",
},
{
name: "file",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.FileScheme,
Path: "some/path/to/place",
src: source.Description{
Metadata: source.FileSourceMetadata{
Path: "some/path/to/place",
},
},
expected: "https://anchore.com/syft/file/my-name-",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := DocumentNamespace(test.inputName, test.srcMetadata)
actual := DocumentNamespace(test.inputName, test.src)
// note: since the namespace ends with a UUID we check the prefix
assert.True(t, strings.HasPrefix(actual, test.expected), fmt.Sprintf("actual namespace %q", actual))
// track each scheme tested (passed or not)
testedSchemes.Add(string(test.srcMetadata.Scheme))
tracker.Tested(t, test.src.Metadata)
})
}
// assert all possible schemes were under test
assert.ElementsMatch(t, allSchemes.List(), testedSchemes.List(), "not all source.Schemes are under test")
}
@@ -28,8 +28,7 @@ func ToSyftModel(doc *spdx.Document) (*sbom.SBOM, error) {
spdxIDMap := make(map[string]interface{})
src := source.Metadata{Scheme: source.UnknownScheme}
src.Scheme = extractSchemeFromNamespace(doc.DocumentNamespace)
src := extractSourceFromNamespace(doc.DocumentNamespace)
s := &sbom.SBOM{
Source: src,
@@ -54,24 +53,32 @@ func ToSyftModel(doc *spdx.Document) (*sbom.SBOM, error) {
// image, directory, for example. This is our best effort to determine
// the scheme. Syft-generated SBOMs have in the namespace
// field a type encoded, which we try to identify here.
func extractSchemeFromNamespace(ns string) source.Scheme {
func extractSourceFromNamespace(ns string) source.Description {
u, err := url.Parse(ns)
if err != nil {
return source.UnknownScheme
return source.Description{
Metadata: nil,
}
}
parts := strings.Split(u.Path, "/")
for _, p := range parts {
switch p {
case inputFile:
return source.FileScheme
return source.Description{
Metadata: source.FileSourceMetadata{},
}
case inputImage:
return source.ImageScheme
return source.Description{
Metadata: source.StereoscopeImageSourceMetadata{},
}
case inputDirectory:
return source.DirectoryScheme
return source.Description{
Metadata: source.DirectorySourceMetadata{},
}
}
}
return source.UnknownScheme
return source.Description{}
}
func findLinuxReleaseByPURL(doc *spdx.Document) *linux.Release {
@@ -1,6 +1,7 @@
package spdxhelpers
import (
"reflect"
"testing"
"github.com/spdx/tools-golang/spdx"
@@ -197,36 +198,46 @@ func Test_extractMetadata(t *testing.T) {
func TestExtractSourceFromNamespaces(t *testing.T) {
tests := []struct {
namespace string
expected source.Scheme
expected any
}{
{
namespace: "https://anchore.com/syft/file/d42b01d0-7325-409b-b03f-74082935c4d3",
expected: source.FileScheme,
expected: source.FileSourceMetadata{},
},
{
namespace: "https://anchore.com/syft/image/d42b01d0-7325-409b-b03f-74082935c4d3",
expected: source.ImageScheme,
expected: source.StereoscopeImageSourceMetadata{},
},
{
namespace: "https://anchore.com/syft/dir/d42b01d0-7325-409b-b03f-74082935c4d3",
expected: source.DirectoryScheme,
expected: source.DirectorySourceMetadata{},
},
{
namespace: "https://another-host/blob/123",
expected: source.UnknownScheme,
expected: nil,
},
{
namespace: "bla bla",
expected: source.UnknownScheme,
expected: nil,
},
{
namespace: "",
expected: source.UnknownScheme,
expected: nil,
},
}
for _, tt := range tests {
require.Equal(t, tt.expected, extractSchemeFromNamespace(tt.namespace))
desc := extractSourceFromNamespace(tt.namespace)
if tt.expected == nil && desc.Metadata == nil {
return
}
if tt.expected != nil && desc.Metadata == nil {
t.Fatal("expected metadata but got nil")
}
if tt.expected == nil && desc.Metadata != nil {
t.Fatal("expected nil metadata but got something")
}
require.Equal(t, reflect.TypeOf(tt.expected), reflect.TypeOf(desc.Metadata))
}
}