mirror of
https://github.com/anchore/syft.git
synced 2026-11-02 07:26:34 +01:00
Split the sbom.Format interface by encode and decode use cases (#2186)
* split up sbom.Format into encode and decode ops Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update cmd pkg to inject format configs Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * bump cyclonedx schema to 1.5 Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * redact image metadata from github encoder tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add more testing around format decoder identify Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add test case for format version options Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix cli tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix CLI test Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * [wip] - review comments Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * keep encoder creation out of post load function Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * keep decider and identify functions Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add a few more doc comments Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * remove format encoder default function helpers Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * address PR feedback Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * move back to streaming based decode functions Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * with common convention for encoder constructors Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix tests and allow for encoders to be created from cli options Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix cli tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix linting Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * buffer reads from stdin to support seeking Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
package model
|
||||
|
||||
// Document represents the syft cataloging findings as a JSON document
|
||||
type Document struct {
|
||||
Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
|
||||
ArtifactRelationships []Relationship `json:"artifactRelationships"`
|
||||
Files []File `json:"files,omitempty"` // note: must have omitempty
|
||||
Secrets []Secrets `json:"secrets,omitempty"` // note: must have omitempty
|
||||
Source Source `json:"source"` // Source represents the original object that was cataloged
|
||||
Distro LinuxRelease `json:"distro"` // Distro represents the Linux distribution that was detected from the source
|
||||
Descriptor Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
|
||||
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
|
||||
}
|
||||
|
||||
// Descriptor describes what created the document as well as surrounding metadata
|
||||
type Descriptor struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Configuration interface{} `json:"configuration,omitempty"`
|
||||
}
|
||||
|
||||
type Schema struct {
|
||||
Version string `json:"version"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
ID string `json:"id"`
|
||||
Location file.Coordinates `json:"location"`
|
||||
Metadata *FileMetadataEntry `json:"metadata,omitempty"`
|
||||
Contents string `json:"contents,omitempty"`
|
||||
Digests []file.Digest `json:"digests,omitempty"`
|
||||
Licenses []FileLicense `json:"licenses,omitempty"`
|
||||
}
|
||||
|
||||
type FileMetadataEntry struct {
|
||||
Mode int `json:"mode"`
|
||||
Type string `json:"type"`
|
||||
LinkDestination string `json:"linkDestination,omitempty"`
|
||||
UserID int `json:"userID"`
|
||||
GroupID int `json:"groupID"`
|
||||
MIMEType string `json:"mimeType"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
type FileLicense struct {
|
||||
Value string `json:"value"`
|
||||
SPDXExpression string `json:"spdxExpression"`
|
||||
Type license.Type `json:"type"`
|
||||
Evidence *FileLicenseEvidence `json:"evidence,omitempty"`
|
||||
}
|
||||
|
||||
type FileLicenseEvidence struct {
|
||||
Confidence int `json:"confidence"`
|
||||
Offset int `json:"offset"`
|
||||
Extent int `json:"extent"`
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type IDLikes []string
|
||||
|
||||
type LinuxRelease struct {
|
||||
PrettyName string `json:"prettyName,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
IDLike IDLikes `json:"idLike,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
VersionID string `json:"versionID,omitempty"`
|
||||
VersionCodename string `json:"versionCodename,omitempty"`
|
||||
BuildID string `json:"buildID,omitempty"`
|
||||
ImageID string `json:"imageID,omitempty"`
|
||||
ImageVersion string `json:"imageVersion,omitempty"`
|
||||
Variant string `json:"variant,omitempty"`
|
||||
VariantID string `json:"variantID,omitempty"`
|
||||
HomeURL string `json:"homeURL,omitempty"`
|
||||
SupportURL string `json:"supportURL,omitempty"`
|
||||
BugReportURL string `json:"bugReportURL,omitempty"`
|
||||
PrivacyPolicyURL string `json:"privacyPolicyURL,omitempty"`
|
||||
CPEName string `json:"cpeName,omitempty"`
|
||||
SupportEnd string `json:"supportEnd,omitempty"`
|
||||
}
|
||||
|
||||
func (s *IDLikes) UnmarshalJSON(data []byte) error {
|
||||
var str string
|
||||
var strSlice []string
|
||||
|
||||
// we support unmarshalling from a single value to support syft json schema v2
|
||||
if err := json.Unmarshal(data, &str); err == nil {
|
||||
*s = []string{str}
|
||||
} else if err := json.Unmarshal(data, &strSlice); err == nil {
|
||||
*s = strSlice
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIDLikes_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data interface{}
|
||||
expected IDLikes
|
||||
}{
|
||||
{
|
||||
name: "single string",
|
||||
data: "well hello there!",
|
||||
expected: IDLikes{
|
||||
"well hello there!",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple strings",
|
||||
data: []string{
|
||||
"well hello there!",
|
||||
"...hello there, john!",
|
||||
},
|
||||
expected: IDLikes{
|
||||
"well hello there!",
|
||||
"...hello there, john!",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, err := json.Marshal(&tt.data)
|
||||
require.NoError(t, err)
|
||||
|
||||
var obj IDLikes
|
||||
require.NoError(t, json.Unmarshal(data, &obj))
|
||||
|
||||
assert.Equal(t, tt.expected, obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"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
|
||||
PackageCustomData
|
||||
}
|
||||
|
||||
// PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package.
|
||||
type PackageBasicData struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type pkg.Type `json:"type"`
|
||||
FoundBy string `json:"foundBy"`
|
||||
Locations []file.Location `json:"locations"`
|
||||
Licenses licenses `json:"licenses"`
|
||||
Language pkg.Language `json:"language"`
|
||||
CPEs []string `json:"cpes"`
|
||||
PURL string `json:"purl"`
|
||||
}
|
||||
|
||||
type licenses []License
|
||||
|
||||
type License struct {
|
||||
Value string `json:"value"`
|
||||
SPDXExpression string `json:"spdxExpression"`
|
||||
Type license.Type `json:"type"`
|
||||
URLs []string `json:"urls"`
|
||||
Locations []file.Location `json:"locations"`
|
||||
}
|
||||
|
||||
func newModelLicensesFromValues(licenses []string) (ml []License) {
|
||||
for _, v := range licenses {
|
||||
expression, err := license.ParseExpression(v)
|
||||
if err != nil {
|
||||
log.Trace("could not find valid spdx expression for %s: %w", v, err)
|
||||
}
|
||||
ml = append(ml, License{
|
||||
Value: v,
|
||||
SPDXExpression: expression,
|
||||
Type: license.Declared,
|
||||
})
|
||||
}
|
||||
return ml
|
||||
}
|
||||
|
||||
func (f *licenses) UnmarshalJSON(b []byte) error {
|
||||
var licenses []License
|
||||
if err := json.Unmarshal(b, &licenses); err != nil {
|
||||
var simpleLicense []string
|
||||
if err := json.Unmarshal(b, &simpleLicense); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal license: %w", err)
|
||||
}
|
||||
licenses = newModelLicensesFromValues(simpleLicense)
|
||||
}
|
||||
*f = licenses
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
|
||||
type PackageCustomData struct {
|
||||
MetadataType pkg.MetadataType `json:"metadataType,omitempty"`
|
||||
Metadata interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// packageMetadataUnpacker is all values needed from Package to disambiguate ambiguous fields during json unmarshaling.
|
||||
type packageMetadataUnpacker struct {
|
||||
MetadataType pkg.MetadataType `json:"metadataType"`
|
||||
Metadata json.RawMessage `json:"metadata"`
|
||||
}
|
||||
|
||||
func (p *packageMetadataUnpacker) String() string {
|
||||
return fmt.Sprintf("metadataType: %s, metadata: %s", p.MetadataType, string(p.Metadata))
|
||||
}
|
||||
|
||||
// UnmarshalJSON is a custom unmarshaller for handling basic values and values with ambiguous types.
|
||||
func (p *Package) UnmarshalJSON(b []byte) error {
|
||||
var basic PackageBasicData
|
||||
if err := json.Unmarshal(b, &basic); err != nil {
|
||||
return err
|
||||
}
|
||||
p.PackageBasicData = basic
|
||||
|
||||
var unpacker packageMetadataUnpacker
|
||||
if err := json.Unmarshal(b, &unpacker); err != nil {
|
||||
log.Warnf("failed to unmarshall into packageMetadataUnpacker: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err := unpackPkgMetadata(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
|
||||
}
|
||||
|
||||
func unpackPkgMetadata(p *Package, unpacker packageMetadataUnpacker) error {
|
||||
p.MetadataType = pkg.CleanMetadataType(unpacker.MetadataType)
|
||||
|
||||
typ, ok := pkg.MetadataTypeByName[p.MetadataType]
|
||||
if ok {
|
||||
val := reflect.New(typ).Interface()
|
||||
if len(unpacker.Metadata) > 0 {
|
||||
if err := json.Unmarshal(unpacker.Metadata, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
p.Metadata = reflect.ValueOf(val).Elem().Interface()
|
||||
return nil
|
||||
}
|
||||
|
||||
// capture unknown metadata as a generic struct
|
||||
if len(unpacker.Metadata) > 0 {
|
||||
var val interface{}
|
||||
if err := json.Unmarshal(unpacker.Metadata, &val); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = val
|
||||
}
|
||||
|
||||
if p.MetadataType != "" {
|
||||
return errUnknownMetadataType
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestUnmarshalPackageGolang(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
packageData []byte
|
||||
assert func(*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": [
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"url": []
|
||||
}
|
||||
],
|
||||
"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": [
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"url": ["https://www.github.com"]
|
||||
},
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"locations": [{"path": "/Users/hal/go/bin/syft"}]
|
||||
}
|
||||
],
|
||||
"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)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can handle package with []string licenses",
|
||||
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": ["MIT", "Apache-2.0"],
|
||||
"language": "go",
|
||||
"cpes": [],
|
||||
"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0"
|
||||
}`),
|
||||
assert: func(p *Package) {
|
||||
assert.Equal(t, licenses{
|
||||
{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
},
|
||||
{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Declared,
|
||||
},
|
||||
}, p.Licenses)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can handle package with []pkg.License licenses",
|
||||
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": [
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared"
|
||||
},
|
||||
{
|
||||
"value": "Apache-2.0",
|
||||
"spdxExpression": "Apache-2.0",
|
||||
"type": "declared"
|
||||
}
|
||||
],
|
||||
"language": "go",
|
||||
"cpes": [],
|
||||
"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0"
|
||||
}`),
|
||||
assert: func(p *Package) {
|
||||
assert.Equal(t, licenses{
|
||||
{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
},
|
||||
{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Declared,
|
||||
},
|
||||
}, p.Licenses)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
wantMetadata interface{}
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "unmarshal package metadata",
|
||||
metadataType: pkg.GolangBinMetadataType,
|
||||
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="
|
||||
}
|
||||
}`),
|
||||
},
|
||||
{
|
||||
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: "can handle RpmdbMetadata",
|
||||
metadataType: pkg.RpmMetadataType,
|
||||
packageData: []byte(`{
|
||||
"id": "4ac699c3b8fe1835",
|
||||
"name": "acl",
|
||||
"version": "2.2.53-1.el8",
|
||||
"type": "rpm",
|
||||
"foundBy": "rpm-db-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/var/lib/rpm/Packages",
|
||||
"layerID": "sha256:74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59"
|
||||
}
|
||||
],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:a:centos:acl:2.2.53-1.el8:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:acl:acl:2.2.53-1.el8:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8",
|
||||
"metadataType": "RpmdbMetadata",
|
||||
"metadata": {
|
||||
"name": "acl",
|
||||
"version": "2.2.53",
|
||||
"epoch": null,
|
||||
"architecture": "x86_64",
|
||||
"release": "1.el8",
|
||||
"sourceRpm": "acl-2.2.53-1.el8.src.rpm",
|
||||
"size": 205740,
|
||||
"license": "GPLv2+",
|
||||
"vendor": "CentOS",
|
||||
"modularityLabel": ""
|
||||
}
|
||||
}`),
|
||||
},
|
||||
{
|
||||
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"
|
||||
}`),
|
||||
},
|
||||
{
|
||||
name: "unknown metadata type",
|
||||
packageData: []byte(`{
|
||||
"metadataType": "NewMetadataType",
|
||||
"metadata": {
|
||||
"thing": "thing-1"
|
||||
}
|
||||
}`),
|
||||
wantErr: require.Error,
|
||||
metadataType: "NewMetadataType",
|
||||
wantMetadata: map[string]interface{}{
|
||||
"thing": "thing-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can handle package with metadata type but missing metadata",
|
||||
packageData: []byte(`{
|
||||
"metadataType": "GolangBinMetadata"
|
||||
}`),
|
||||
metadataType: pkg.GolangBinMetadataType,
|
||||
wantMetadata: pkg.GolangBinMetadata{},
|
||||
},
|
||||
{
|
||||
name: "can handle package with golang bin metadata type",
|
||||
packageData: []byte(`{
|
||||
"metadataType": "GolangBinMetadata"
|
||||
}`),
|
||||
metadataType: pkg.GolangBinMetadataType,
|
||||
wantMetadata: pkg.GolangBinMetadata{},
|
||||
},
|
||||
{
|
||||
name: "can handle package with unknonwn metadata type and missing metadata",
|
||||
packageData: []byte(`{
|
||||
"metadataType": "BadMetadata"
|
||||
}`),
|
||||
wantErr: require.Error,
|
||||
metadataType: "BadMetadata",
|
||||
wantMetadata: nil,
|
||||
},
|
||||
{
|
||||
name: "can handle package with unknonwn metadata type and metadata",
|
||||
packageData: []byte(`{
|
||||
"metadataType": "BadMetadata",
|
||||
"metadata": {
|
||||
"random": "thing"
|
||||
}
|
||||
}`),
|
||||
wantErr: require.Error,
|
||||
metadataType: "BadMetadata",
|
||||
wantMetadata: map[string]interface{}{
|
||||
"random": "thing",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if test.wantErr == nil {
|
||||
test.wantErr = require.NoError
|
||||
}
|
||||
p := &Package{}
|
||||
|
||||
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 := unpackPkgMetadata(p, unpacker)
|
||||
assert.Equal(t, test.metadataType, p.MetadataType)
|
||||
test.wantErr(t, err)
|
||||
|
||||
if test.wantMetadata != nil {
|
||||
assert.True(t, reflect.DeepEqual(test.wantMetadata, p.Metadata))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package model
|
||||
|
||||
type Relationship struct {
|
||||
Parent string `json:"parent"`
|
||||
Child string `json:"child"`
|
||||
Type string `json:"type"`
|
||||
Metadata interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
type Secrets struct {
|
||||
Location file.Coordinates `json:"location"`
|
||||
Secrets []file.SearchResult `json:"secrets"`
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/internal/sourcemetadata"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Source object represents the thing that was cataloged
|
||||
type Source struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type string `json:"type"`
|
||||
Metadata interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
// sourceUnpacker is used to unmarshal Source objects
|
||||
type sourceUnpacker struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type string `json:"type"`
|
||||
Metadata json.RawMessage `json:"metadata"`
|
||||
Target json.RawMessage `json:"target"` // pre-v9 schema support
|
||||
}
|
||||
|
||||
// UnmarshalJSON populates a source object from JSON bytes.
|
||||
func (s *Source) UnmarshalJSON(b []byte) error {
|
||||
var unpacker sourceUnpacker
|
||||
err := json.Unmarshal(b, &unpacker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Name = unpacker.Name
|
||||
s.Version = unpacker.Version
|
||||
s.Type = unpacker.Type
|
||||
s.ID = unpacker.ID
|
||||
|
||||
if len(unpacker.Target) > 0 {
|
||||
s.Type = cleanPreSchemaV9MetadataType(s.Type)
|
||||
s.Metadata, err = extractPreSchemaV9Metadata(s.Type, unpacker.Target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to extract pre-schema-v9 source metadata: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return unpackSrcMetadata(s, unpacker)
|
||||
}
|
||||
|
||||
func unpackSrcMetadata(s *Source, unpacker sourceUnpacker) error {
|
||||
rt := sourcemetadata.ReflectTypeFromJSONName(s.Type)
|
||||
if rt == nil {
|
||||
return fmt.Errorf("unable to find source metadata type=%q", s.Type)
|
||||
}
|
||||
|
||||
val := reflect.New(rt).Interface()
|
||||
if len(unpacker.Metadata) > 0 {
|
||||
if err := json.Unmarshal(unpacker.Metadata, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.Metadata = reflect.ValueOf(val).Elem().Interface()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanPreSchemaV9MetadataType(t string) string {
|
||||
t = strings.ToLower(t)
|
||||
if t == "dir" {
|
||||
return "directory"
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func extractPreSchemaV9Metadata(t string, target []byte) (interface{}, error) {
|
||||
switch t {
|
||||
case "directory", "dir":
|
||||
cleanTarget, err := strconv.Unquote(string(target))
|
||||
if err != nil {
|
||||
cleanTarget = string(target)
|
||||
}
|
||||
|
||||
return source.DirectorySourceMetadata{
|
||||
Path: cleanTarget,
|
||||
}, nil
|
||||
|
||||
case "file":
|
||||
cleanTarget, err := strconv.Unquote(string(target))
|
||||
if err != nil {
|
||||
cleanTarget = string(target)
|
||||
}
|
||||
|
||||
return source.FileSourceMetadata{
|
||||
Path: cleanTarget,
|
||||
}, nil
|
||||
|
||||
case "image":
|
||||
var payload source.StereoscopeImageSourceMetadata
|
||||
if err := json.Unmarshal(target, &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return payload, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported package metadata type: %+v", t)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/sourcemetadata"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestSource_UnmarshalJSON(t *testing.T) {
|
||||
tracker := sourcemetadata.NewCompletionTester(t)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expected *Source
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "directory",
|
||||
input: []byte(`{
|
||||
"id": "foobar",
|
||||
"type": "directory",
|
||||
"metadata": {"path": "/var/lib/foo", "base":"/nope"}
|
||||
}`),
|
||||
expected: &Source{
|
||||
ID: "foobar",
|
||||
Type: "directory",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Path: "/var/lib/foo",
|
||||
//Base: "/nope", // note: should be ignored entirely
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
input: []byte(`{
|
||||
"id": "foobar",
|
||||
"type": "image",
|
||||
"metadata": {
|
||||
"userInput": "alpine:3.10",
|
||||
"imageID": "sha256:e7b300aee9f9bf3433d32bc9305bfdd22183beb59d933b48d77ab56ba53a197a",
|
||||
"manifestDigest": "sha256:e515aad2ed234a5072c4d2ef86a1cb77d5bfe4b11aa865d9214875734c4eeb3c",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [],
|
||||
"imageSize": 5576169,
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635",
|
||||
"size": 5576169
|
||||
}
|
||||
],
|
||||
"manifest": "ewogICAic2NoZW1hVmVyc2lvbiI6IDIsCiAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRvY2tlci5kaXN0cmlidXRpb24ubWFuaWZlc3QudjIranNvbiIsCiAgICJjb25maWciOiB7CiAgICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRvY2tlci5jb250YWluZXIuaW1hZ2UudjEranNvbiIsCiAgICAgICJzaXplIjogMTQ3MiwKICAgICAgImRpZ2VzdCI6ICJzaGEyNTY6ZTdiMzAwYWVlOWY5YmYzNDMzZDMyYmM5MzA1YmZkZDIyMTgzYmViNTlkOTMzYjQ4ZDc3YWI1NmJhNTNhMTk3YSIKICAgfSwKICAgImxheWVycyI6IFsKICAgICAgewogICAgICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuaW1hZ2Uucm9vdGZzLmRpZmYudGFyLmd6aXAiLAogICAgICAgICAic2l6ZSI6IDI3OTgzMzgsCiAgICAgICAgICJkaWdlc3QiOiAic2hhMjU2OjM5NmMzMTgzNzExNmFjMjkwNDU4YWZjYjkyOGY2OGI2Y2MxYzdiZGQ2OTYzZmM3MmY1MmYzNjVhMmE4OWMxYjUiCiAgICAgIH0KICAgXQp9",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJIb3N0bmFtZSI6IiIsIkRvbWFpbm5hbWUiOiIiLCJVc2VyIjoiIiwiQXR0YWNoU3RkaW4iOmZhbHNlLCJBdHRhY2hTdGRvdXQiOmZhbHNlLCJBdHRhY2hTdGRlcnIiOmZhbHNlLCJUdHkiOmZhbHNlLCJPcGVuU3RkaW4iOmZhbHNlLCJTdGRpbk9uY2UiOmZhbHNlLCJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiQ21kIjpbIi9iaW4vc2giXSwiSW1hZ2UiOiJzaGEyNTY6ZWIyMDgwYzQ1NWU5NGMyMmFlMzViM2FlZjllMDc4YzQ5MmEwMDc5NTQxMmUwMjZlNGQ2YjQxZWY2NGJjN2RkOCIsIlZvbHVtZXMiOm51bGwsIldvcmtpbmdEaXIiOiIiLCJFbnRyeXBvaW50IjpudWxsLCJPbkJ1aWxkIjpudWxsLCJMYWJlbHMiOm51bGx9LCJjb250YWluZXIiOiJmZGI3ZTgwZTMzMzllOGQwNTk5MjgyZTYwNmM5MDdhYTU4ODFlZTRjNjY4YTY4MTM2MTE5ZTZkZmFjNmNlM2E0IiwiY29udGFpbmVyX2NvbmZpZyI6eyJIb3N0bmFtZSI6ImZkYjdlODBlMzMzOSIsIkRvbWFpbm5hbWUiOiIiLCJVc2VyIjoiIiwiQXR0YWNoU3RkaW4iOmZhbHNlLCJBdHRhY2hTdGRvdXQiOmZhbHNlLCJBdHRhY2hTdGRlcnIiOmZhbHNlLCJUdHkiOmZhbHNlLCJPcGVuU3RkaW4iOmZhbHNlLCJTdGRpbk9uY2UiOmZhbHNlLCJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiQ21kIjpbIi9iaW4vc2giLCItYyIsIiMobm9wKSAiLCJDTUQgW1wiL2Jpbi9zaFwiXSJdLCJJbWFnZSI6InNoYTI1NjplYjIwODBjNDU1ZTk0YzIyYWUzNWIzYWVmOWUwNzhjNDkyYTAwNzk1NDEyZTAyNmU0ZDZiNDFlZjY0YmM3ZGQ4IiwiVm9sdW1lcyI6bnVsbCwiV29ya2luZ0RpciI6IiIsIkVudHJ5cG9pbnQiOm51bGwsIk9uQnVpbGQiOm51bGwsIkxhYmVscyI6e319LCJjcmVhdGVkIjoiMjAyMS0wNC0xNFQxOToyMDowNS4zMzgzOTc3NjFaIiwiZG9ja2VyX3ZlcnNpb24iOiIxOS4wMy4xMiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIxLTA0LTE0VDE5OjIwOjA0Ljk4NzIxOTEyNFoiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgQUREIGZpbGU6YzUzNzdlYWE5MjZiZjQxMmRkOGQ0YTA4YjBhMWYyMzk5Y2ZkNzA4NzQzNTMzYjBhYTAzYjUzZDE0Y2I0YmI0ZSBpbiAvICJ9LHsiY3JlYXRlZCI6IjIwMjEtMDQtMTRUMTk6MjA6MDUuMzM4Mzk3NzYxWiIsImNyZWF0ZWRfYnkiOiIvYmluL3NoIC1jICMobm9wKSAgQ01EIFtcIi9iaW4vc2hcIl0iLCJlbXB0eV9sYXllciI6dHJ1ZX1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6OWZiM2FhMmY4YjgwMjNhNGJlYmJmOTJhYTU2N2NhZjg4ZTM4ZTk2OWFkYTlmMGFjMTI2NDNiMjg0NzM5MTYzNSJdfX0=",
|
||||
"repoDigests": [
|
||||
"index.docker.io/library/alpine@sha256:451eee8bedcb2f029756dc3e9d73bab0e7943c1ac55cff3a4861c52a0fdd3e98"
|
||||
]
|
||||
}
|
||||
}`),
|
||||
expected: &Source{
|
||||
ID: "foobar",
|
||||
Type: "image",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
UserInput: "alpine:3.10",
|
||||
ID: "sha256:e7b300aee9f9bf3433d32bc9305bfdd22183beb59d933b48d77ab56ba53a197a",
|
||||
ManifestDigest: "sha256:e515aad2ed234a5072c4d2ef86a1cb77d5bfe4b11aa865d9214875734c4eeb3c",
|
||||
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
|
||||
Tags: []string{},
|
||||
Size: 5576169,
|
||||
Layers: []source.StereoscopeLayerMetadata{
|
||||
{
|
||||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Digest: "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635",
|
||||
Size: 5576169,
|
||||
},
|
||||
},
|
||||
RawManifest: []byte(`{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"config": {
|
||||
"mediaType": "application/vnd.docker.container.image.v1+json",
|
||||
"size": 1472,
|
||||
"digest": "sha256:e7b300aee9f9bf3433d32bc9305bfdd22183beb59d933b48d77ab56ba53a197a"
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"size": 2798338,
|
||||
"digest": "sha256:396c31837116ac290458afcb928f68b6cc1c7bdd6963fc72f52f365a2a89c1b5"
|
||||
}
|
||||
]
|
||||
}`),
|
||||
RawConfig: []byte(`{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh"],"Image":"sha256:eb2080c455e94c22ae35b3aef9e078c492a00795412e026e4d6b41ef64bc7dd8","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"fdb7e80e3339e8d0599282e606c907aa5881ee4c668a68136119e6dfac6ce3a4","container_config":{"Hostname":"fdb7e80e3339","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/bin/sh\"]"],"Image":"sha256:eb2080c455e94c22ae35b3aef9e078c492a00795412e026e4d6b41ef64bc7dd8","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2021-04-14T19:20:05.338397761Z","docker_version":"19.03.12","history":[{"created":"2021-04-14T19:20:04.987219124Z","created_by":"/bin/sh -c #(nop) ADD file:c5377eaa926bf412dd8d4a08b0a1f2399cfd708743533b0aa03b53d14cb4bb4e in / "},{"created":"2021-04-14T19:20:05.338397761Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/sh\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635"]}}`),
|
||||
RepoDigests: []string{
|
||||
"index.docker." +
|
||||
"io/library/alpine@sha256:451eee8bedcb2f029756dc3e9d73bab0e7943c1ac55cff3a4861c52a0fdd3e98",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
input: []byte(`{
|
||||
"id": "foobar",
|
||||
"type": "file",
|
||||
"metadata": {
|
||||
"path": "/var/lib/foo/go.mod",
|
||||
"mimeType": "text/plain",
|
||||
"digests": [
|
||||
{
|
||||
"algorithm": "sha256",
|
||||
"value": "e7b300aee9f9bf3433d32bc9305bfdd22183beb59d933b48d77ab56ba53a197a"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`),
|
||||
expected: &Source{
|
||||
ID: "foobar",
|
||||
Type: "file",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Path: "/var/lib/foo/go.mod",
|
||||
Digests: []file.Digest{
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "e7b300aee9f9bf3433d32bc9305bfdd22183beb59d933b48d77ab56ba53a197a",
|
||||
},
|
||||
},
|
||||
MIMEType: "text/plain",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown source type",
|
||||
input: []byte(`{
|
||||
"id": "foobar",
|
||||
"type": "unknown-thing",
|
||||
"target":"/var/lib/foo"
|
||||
}`),
|
||||
expected: &Source{
|
||||
ID: "foobar",
|
||||
Type: "unknown-thing",
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
|
||||
var src Source
|
||||
err := json.Unmarshal(tt.input, &src)
|
||||
|
||||
tt.wantErr(t, err)
|
||||
|
||||
if diff := cmp.Diff(tt.expected, &src); diff != "" {
|
||||
t.Errorf("unexpected result from Source unmarshaling (-want +got)\n%s", diff)
|
||||
}
|
||||
|
||||
tracker.Tested(t, tt.expected.Metadata)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSource_UnmarshalJSON_PreSchemaV9(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectedSource *Source
|
||||
errAssertion assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "abbreviated directory",
|
||||
input: []byte(`{
|
||||
"id": "foobar",
|
||||
"type": "dir",
|
||||
"target":"/var/lib/foo"
|
||||
}`),
|
||||
expectedSource: &Source{
|
||||
ID: "foobar",
|
||||
Type: "directory",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Path: "/var/lib/foo",
|
||||
},
|
||||
},
|
||||
errAssertion: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "directory",
|
||||
input: []byte(`{
|
||||
"id": "foobar",
|
||||
"type": "directory",
|
||||
"target":"/var/lib/foo"
|
||||
}`),
|
||||
expectedSource: &Source{
|
||||
ID: "foobar",
|
||||
Type: "directory",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Path: "/var/lib/foo",
|
||||
},
|
||||
},
|
||||
errAssertion: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
input: []byte(`{
|
||||
"id": "foobar",
|
||||
"type": "image",
|
||||
"target": {
|
||||
"userInput": "alpine:3.10",
|
||||
"imageID": "sha256:e7b300aee9f9bf3433d32bc9305bfdd22183beb59d933b48d77ab56ba53a197a",
|
||||
"manifestDigest": "sha256:e515aad2ed234a5072c4d2ef86a1cb77d5bfe4b11aa865d9214875734c4eeb3c",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [],
|
||||
"imageSize": 5576169,
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635",
|
||||
"size": 5576169
|
||||
}
|
||||
],
|
||||
"manifest": "ewogICAic2NoZW1hVmVyc2lvbiI6IDIsCiAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRvY2tlci5kaXN0cmlidXRpb24ubWFuaWZlc3QudjIranNvbiIsCiAgICJjb25maWciOiB7CiAgICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRvY2tlci5jb250YWluZXIuaW1hZ2UudjEranNvbiIsCiAgICAgICJzaXplIjogMTQ3MiwKICAgICAgImRpZ2VzdCI6ICJzaGEyNTY6ZTdiMzAwYWVlOWY5YmYzNDMzZDMyYmM5MzA1YmZkZDIyMTgzYmViNTlkOTMzYjQ4ZDc3YWI1NmJhNTNhMTk3YSIKICAgfSwKICAgImxheWVycyI6IFsKICAgICAgewogICAgICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuaW1hZ2Uucm9vdGZzLmRpZmYudGFyLmd6aXAiLAogICAgICAgICAic2l6ZSI6IDI3OTgzMzgsCiAgICAgICAgICJkaWdlc3QiOiAic2hhMjU2OjM5NmMzMTgzNzExNmFjMjkwNDU4YWZjYjkyOGY2OGI2Y2MxYzdiZGQ2OTYzZmM3MmY1MmYzNjVhMmE4OWMxYjUiCiAgICAgIH0KICAgXQp9",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJIb3N0bmFtZSI6IiIsIkRvbWFpbm5hbWUiOiIiLCJVc2VyIjoiIiwiQXR0YWNoU3RkaW4iOmZhbHNlLCJBdHRhY2hTdGRvdXQiOmZhbHNlLCJBdHRhY2hTdGRlcnIiOmZhbHNlLCJUdHkiOmZhbHNlLCJPcGVuU3RkaW4iOmZhbHNlLCJTdGRpbk9uY2UiOmZhbHNlLCJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiQ21kIjpbIi9iaW4vc2giXSwiSW1hZ2UiOiJzaGEyNTY6ZWIyMDgwYzQ1NWU5NGMyMmFlMzViM2FlZjllMDc4YzQ5MmEwMDc5NTQxMmUwMjZlNGQ2YjQxZWY2NGJjN2RkOCIsIlZvbHVtZXMiOm51bGwsIldvcmtpbmdEaXIiOiIiLCJFbnRyeXBvaW50IjpudWxsLCJPbkJ1aWxkIjpudWxsLCJMYWJlbHMiOm51bGx9LCJjb250YWluZXIiOiJmZGI3ZTgwZTMzMzllOGQwNTk5MjgyZTYwNmM5MDdhYTU4ODFlZTRjNjY4YTY4MTM2MTE5ZTZkZmFjNmNlM2E0IiwiY29udGFpbmVyX2NvbmZpZyI6eyJIb3N0bmFtZSI6ImZkYjdlODBlMzMzOSIsIkRvbWFpbm5hbWUiOiIiLCJVc2VyIjoiIiwiQXR0YWNoU3RkaW4iOmZhbHNlLCJBdHRhY2hTdGRvdXQiOmZhbHNlLCJBdHRhY2hTdGRlcnIiOmZhbHNlLCJUdHkiOmZhbHNlLCJPcGVuU3RkaW4iOmZhbHNlLCJTdGRpbk9uY2UiOmZhbHNlLCJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiQ21kIjpbIi9iaW4vc2giLCItYyIsIiMobm9wKSAiLCJDTUQgW1wiL2Jpbi9zaFwiXSJdLCJJbWFnZSI6InNoYTI1NjplYjIwODBjNDU1ZTk0YzIyYWUzNWIzYWVmOWUwNzhjNDkyYTAwNzk1NDEyZTAyNmU0ZDZiNDFlZjY0YmM3ZGQ4IiwiVm9sdW1lcyI6bnVsbCwiV29ya2luZ0RpciI6IiIsIkVudHJ5cG9pbnQiOm51bGwsIk9uQnVpbGQiOm51bGwsIkxhYmVscyI6e319LCJjcmVhdGVkIjoiMjAyMS0wNC0xNFQxOToyMDowNS4zMzgzOTc3NjFaIiwiZG9ja2VyX3ZlcnNpb24iOiIxOS4wMy4xMiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIxLTA0LTE0VDE5OjIwOjA0Ljk4NzIxOTEyNFoiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgQUREIGZpbGU6YzUzNzdlYWE5MjZiZjQxMmRkOGQ0YTA4YjBhMWYyMzk5Y2ZkNzA4NzQzNTMzYjBhYTAzYjUzZDE0Y2I0YmI0ZSBpbiAvICJ9LHsiY3JlYXRlZCI6IjIwMjEtMDQtMTRUMTk6MjA6MDUuMzM4Mzk3NzYxWiIsImNyZWF0ZWRfYnkiOiIvYmluL3NoIC1jICMobm9wKSAgQ01EIFtcIi9iaW4vc2hcIl0iLCJlbXB0eV9sYXllciI6dHJ1ZX1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6OWZiM2FhMmY4YjgwMjNhNGJlYmJmOTJhYTU2N2NhZjg4ZTM4ZTk2OWFkYTlmMGFjMTI2NDNiMjg0NzM5MTYzNSJdfX0=",
|
||||
"repoDigests": [
|
||||
"index.docker.io/library/alpine@sha256:451eee8bedcb2f029756dc3e9d73bab0e7943c1ac55cff3a4861c52a0fdd3e98"
|
||||
]
|
||||
}
|
||||
}`),
|
||||
expectedSource: &Source{
|
||||
ID: "foobar",
|
||||
Type: "image",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
UserInput: "alpine:3.10",
|
||||
ID: "sha256:e7b300aee9f9bf3433d32bc9305bfdd22183beb59d933b48d77ab56ba53a197a",
|
||||
ManifestDigest: "sha256:e515aad2ed234a5072c4d2ef86a1cb77d5bfe4b11aa865d9214875734c4eeb3c",
|
||||
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
|
||||
Tags: []string{},
|
||||
Size: 5576169,
|
||||
Layers: []source.StereoscopeLayerMetadata{
|
||||
{
|
||||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Digest: "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635",
|
||||
Size: 5576169,
|
||||
},
|
||||
},
|
||||
RawManifest: []byte(`{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"config": {
|
||||
"mediaType": "application/vnd.docker.container.image.v1+json",
|
||||
"size": 1472,
|
||||
"digest": "sha256:e7b300aee9f9bf3433d32bc9305bfdd22183beb59d933b48d77ab56ba53a197a"
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"size": 2798338,
|
||||
"digest": "sha256:396c31837116ac290458afcb928f68b6cc1c7bdd6963fc72f52f365a2a89c1b5"
|
||||
}
|
||||
]
|
||||
}`),
|
||||
RawConfig: []byte(`{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh"],"Image":"sha256:eb2080c455e94c22ae35b3aef9e078c492a00795412e026e4d6b41ef64bc7dd8","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"fdb7e80e3339e8d0599282e606c907aa5881ee4c668a68136119e6dfac6ce3a4","container_config":{"Hostname":"fdb7e80e3339","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/bin/sh\"]"],"Image":"sha256:eb2080c455e94c22ae35b3aef9e078c492a00795412e026e4d6b41ef64bc7dd8","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2021-04-14T19:20:05.338397761Z","docker_version":"19.03.12","history":[{"created":"2021-04-14T19:20:04.987219124Z","created_by":"/bin/sh -c #(nop) ADD file:c5377eaa926bf412dd8d4a08b0a1f2399cfd708743533b0aa03b53d14cb4bb4e in / "},{"created":"2021-04-14T19:20:05.338397761Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/sh\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635"]}}`),
|
||||
RepoDigests: []string{
|
||||
"index.docker." +
|
||||
"io/library/alpine@sha256:451eee8bedcb2f029756dc3e9d73bab0e7943c1ac55cff3a4861c52a0fdd3e98",
|
||||
},
|
||||
},
|
||||
},
|
||||
errAssertion: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
input: []byte(`{
|
||||
"id": "foobar",
|
||||
"type": "file",
|
||||
"target":"/var/lib/foo/go.mod"
|
||||
}`),
|
||||
expectedSource: &Source{
|
||||
ID: "foobar",
|
||||
Type: "file",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Path: "/var/lib/foo/go.mod",
|
||||
},
|
||||
},
|
||||
errAssertion: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "unknown source type",
|
||||
input: []byte(`{
|
||||
"id": "foobar",
|
||||
"type": "unknown-thing",
|
||||
"target":"/var/lib/foo"
|
||||
}`),
|
||||
expectedSource: &Source{
|
||||
ID: "foobar",
|
||||
Type: "unknown-thing",
|
||||
},
|
||||
errAssertion: assert.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
src := new(Source)
|
||||
|
||||
err := json.Unmarshal(testCase.input, src)
|
||||
|
||||
testCase.errAssertion(t, err)
|
||||
if diff := cmp.Diff(testCase.expectedSource, src); diff != "" {
|
||||
t.Errorf("unexpected result from Source unmarshaling (-want +got)\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user