diff --git a/internal/formats/syftjson/model/package.go b/internal/formats/syftjson/model/package.go index 09fdf07ef..f3258afdf 100644 --- a/internal/formats/syftjson/model/package.go +++ b/internal/formats/syftjson/model/package.go @@ -2,6 +2,7 @@ package model import ( "encoding/json" + "errors" "fmt" "github.com/anchore/syft/syft/source" @@ -10,6 +11,8 @@ import ( "github.com/anchore/syft/syft/pkg" ) +var errUnknownMetadataType = errors.New("unknown metadata type") + // Package represents a pkg.Package object specialized for JSON marshaling and unmarshalling. type Package struct { PackageBasicData @@ -60,13 +63,22 @@ func (p *Package) UnmarshalJSON(b []byte) error { return err } - return unpackMetadata(p, unpacker) + err := unpackMetadata(p, unpacker) + if errors.Is(err, errUnknownMetadataType) { + log.Warnf("unknown package metadata type=%q for packageID=%q", p.MetadataType, p.ID) + return nil + } + + return err } // nolint:funlen,gocognit,gocyclo func unpackMetadata(p *Package, unpacker packageMetadataUnpacker) error { p.MetadataType = unpacker.MetadataType switch p.MetadataType { + case "": + // there is no metadata, skip + break case pkg.AlpmMetadataType: var payload pkg.AlpmMetadata if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil { @@ -176,8 +188,7 @@ func unpackMetadata(p *Package, unpacker packageMetadataUnpacker) error { } p.Metadata = payload default: - log.Warnf("unknown package metadata type=%q for packageID=%q", p.MetadataType, p.ID) + return errUnknownMetadataType } - return nil } diff --git a/internal/formats/syftjson/model/package_test.go b/internal/formats/syftjson/model/package_test.go index a31639c25..81d55bc76 100644 --- a/internal/formats/syftjson/model/package_test.go +++ b/internal/formats/syftjson/model/package_test.go @@ -1,21 +1,95 @@ package model import ( - "testing" - + "encoding/json" "github.com/anchore/syft/syft/pkg" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" ) func TestUnmarshalPackageGolang(t *testing.T) { tests := []struct { name string - p *Package packageData []byte + assert func(*Package) }{ { - name: "Package.UnmarshalJSON unmarshals PackageBasicData", - p: &Package{}, + name: "unmarshal package metadata", + packageData: []byte(`{ + "id": "8b594519bc23da50", + "name": "gopkg.in/square/go-jose.v2", + "version": "v2.6.0", + "type": "go-module", + "foundBy": "go-module-binary-cataloger", + "locations": [ + { + "path": "/Users/hal/go/bin/syft" + } + ], + "licenses": [], + "language": "go", + "cpes": [], + "purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0", + "metadataType": "GolangBinMetadata", + "metadata": { + "goCompiledVersion": "go1.18", + "architecture": "amd64", + "h1Digest": "h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=" + } + }`), + assert: func(p *Package) { + assert.NotNil(t, p.Metadata) + golangMetadata := p.Metadata.(pkg.GolangBinMetadata) + assert.NotEmpty(t, golangMetadata) + assert.Equal(t, "go1.18", golangMetadata.GoCompiledVersion) + }, + }, + { + name: "can handle package without metadata", + packageData: []byte(`{ + "id": "8b594519bc23da50", + "name": "gopkg.in/square/go-jose.v2", + "version": "v2.6.0", + "type": "go-module", + "foundBy": "go-mod-cataloger", + "locations": [ + { + "path": "/Users/hal/go/bin/syft" + } + ], + "licenses": [], + "language": "go", + "cpes": [], + "purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0" + }`), + assert: func(p *Package) { + assert.Empty(t, p.MetadataType) + assert.Empty(t, p.Metadata) + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + p := &Package{} + err := p.UnmarshalJSON(test.packageData) + require.NoError(t, err) + test.assert(p) + }) + } +} + +func Test_unpackMetadata(t *testing.T) { + tests := []struct { + name string + packageData []byte + metadataType pkg.MetadataType + wantErr require.ErrorAssertionFunc + }{ + { + name: "unmarshal package metadata", + metadataType: pkg.GolangBinMetadataType, packageData: []byte(`{ "id": "8b594519bc23da50", "name": "gopkg.in/square/go-jose.v2", @@ -39,19 +113,67 @@ func TestUnmarshalPackageGolang(t *testing.T) { } }`), }, + { + name: "can handle package without metadata", + metadataType: "", + packageData: []byte(`{ + "id": "8b594519bc23da50", + "name": "gopkg.in/square/go-jose.v2", + "version": "v2.6.0", + "type": "go-module", + "foundBy": "go-mod-cataloger", + "locations": [ + { + "path": "/Users/hal/go/bin/syft" + } + ], + "licenses": [], + "language": "go", + "cpes": [], + "purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0" + }`), + }, + { + name: "bad metadata type is an error", + metadataType: "BOGOSITY", + wantErr: require.Error, + packageData: []byte(`{ + "id": "8b594519bc23da50", + "name": "gopkg.in/square/go-jose.v2", + "version": "v2.6.0", + "type": "go-module", + "foundBy": "go-mod-cataloger", + "locations": [ + { + "path": "/Users/hal/go/bin/syft" + } + ], + "licenses": [], + "language": "go", + "cpes": [], + "purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0", + "metadataType": "BOGOSITY" + }`), + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := test.p.UnmarshalJSON(test.packageData) - if err != nil { - t.Fatalf("could not unmarshal packageData: %v", err) + if test.wantErr == nil { + test.wantErr = require.NoError } + p := &Package{} - assert.NotNil(t, test.p.Metadata) - golangMetadata := test.p.Metadata.(pkg.GolangBinMetadata) - assert.NotEmpty(t, golangMetadata) - assert.Equal(t, "go1.18", golangMetadata.GoCompiledVersion) + var basic PackageBasicData + require.NoError(t, json.Unmarshal(test.packageData, &basic)) + p.PackageBasicData = basic + + var unpacker packageMetadataUnpacker + require.NoError(t, json.Unmarshal(test.packageData, &unpacker)) + + err := unpackMetadata(p, unpacker) + assert.Equal(t, test.metadataType, p.MetadataType) + test.wantErr(t, err) }) } }