mirror of
https://github.com/anchore/syft.git
synced 2025-11-18 17:03:17 +01:00
migrate json presenter to json format object
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
9ded1c4c22
commit
c1346ad62c
24
internal/formats/syftjson/decoder.go
Normal file
24
internal/formats/syftjson/decoder.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package syftjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/formats/syftjson/model"
|
||||||
|
"github.com/anchore/syft/syft/distro"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
func decoder(reader io.Reader) (*pkg.Catalog, *source.Metadata, *distro.Distro, error) {
|
||||||
|
dec := json.NewDecoder(reader)
|
||||||
|
|
||||||
|
var doc model.Document
|
||||||
|
err := dec.Decode(&doc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("unable to decode syft-json: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return toSyftModel(doc)
|
||||||
|
}
|
||||||
50
internal/formats/syftjson/decoder_test.go
Normal file
50
internal/formats/syftjson/decoder_test.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package syftjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||||
|
"github.com/go-test/deep"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodeDecodeCycle(t *testing.T) {
|
||||||
|
testImage := "image-simple"
|
||||||
|
originalCatalog, originalMetadata, _ := testutils.ImageInput(t, testImage)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
assert.NoError(t, encoder(&buf, originalCatalog, &originalMetadata, nil))
|
||||||
|
|
||||||
|
actualCatalog, actualMetadata, _, err := decoder(bytes.NewReader(buf.Bytes()))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for _, d := range deep.Equal(originalMetadata, *actualMetadata) {
|
||||||
|
t.Errorf("metadata difference: %+v", d)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualPackages := actualCatalog.Sorted()
|
||||||
|
for idx, p := range originalCatalog.Sorted() {
|
||||||
|
if !assert.Equal(t, p.Name, actualPackages[idx].Name) {
|
||||||
|
t.Errorf("different package at idx=%d: %s vs %s", idx, p.Name, actualPackages[idx].Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// ids will never be equal
|
||||||
|
p.ID = ""
|
||||||
|
actualPackages[idx].ID = ""
|
||||||
|
|
||||||
|
for _, d := range deep.Equal(*p, *actualPackages[idx]) {
|
||||||
|
if strings.Contains(d, ".VirtualPath: ") {
|
||||||
|
// location.Virtual path is not exposed in the json output
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(d, "<nil slice> != []") {
|
||||||
|
// semantically the same
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Errorf("package difference (%s): %+v", p.Name, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
internal/formats/syftjson/encoder.go
Normal file
23
internal/formats/syftjson/encoder.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package syftjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/distro"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
func encoder(output io.Writer, catalog *pkg.Catalog, srcMetadata *source.Metadata, d *distro.Distro) error {
|
||||||
|
// TODO: application config not available yet
|
||||||
|
doc := ToFormatModel(catalog, srcMetadata, d, nil)
|
||||||
|
|
||||||
|
enc := json.NewEncoder(output)
|
||||||
|
// prevent > and < from being escaped in the payload
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
|
||||||
|
return enc.Encode(&doc)
|
||||||
|
}
|
||||||
12
internal/formats/syftjson/format.go
Normal file
12
internal/formats/syftjson/format.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package syftjson
|
||||||
|
|
||||||
|
import "github.com/anchore/syft/syft/format"
|
||||||
|
|
||||||
|
func Format() format.Format {
|
||||||
|
return format.NewFormat(
|
||||||
|
format.JSONOption,
|
||||||
|
encoder,
|
||||||
|
decoder,
|
||||||
|
validator,
|
||||||
|
)
|
||||||
|
}
|
||||||
8
internal/formats/syftjson/model/distro.go
Normal file
8
internal/formats/syftjson/model/distro.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// Distro provides information about a detected Linux Distro.
|
||||||
|
type Distro struct {
|
||||||
|
Name string `json:"name"` // Name of the Linux distribution
|
||||||
|
Version string `json:"version"` // Version of the Linux distribution (major or major.minor version)
|
||||||
|
IDLike string `json:"idLike"` // the ID_LIKE field found within the /etc/os-release file
|
||||||
|
}
|
||||||
23
internal/formats/syftjson/model/document.go
Normal file
23
internal/formats/syftjson/model/document.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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"`
|
||||||
|
Source Source `json:"source"` // Source represents the original object that was cataloged
|
||||||
|
Distro Distro `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"`
|
||||||
|
}
|
||||||
122
internal/formats/syftjson/model/package.go
Normal file
122
internal/formats/syftjson/model/package.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Package represents a pkg.Package object specialized for JSON marshaling and unmarshaling.
|
||||||
|
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 []source.Location `json:"locations"`
|
||||||
|
Licenses []string `json:"licenses"`
|
||||||
|
Language pkg.Language `json:"language"`
|
||||||
|
CPEs []string `json:"cpes"`
|
||||||
|
PURL string `json:"purl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
|
||||||
|
type PackageCustomData struct {
|
||||||
|
MetadataType pkg.MetadataType `json:"metadataType"`
|
||||||
|
Metadata interface{} `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
p.MetadataType = unpacker.MetadataType
|
||||||
|
|
||||||
|
switch p.MetadataType {
|
||||||
|
case pkg.ApkMetadataType:
|
||||||
|
var payload pkg.ApkMetadata
|
||||||
|
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Metadata = payload
|
||||||
|
case pkg.RpmdbMetadataType:
|
||||||
|
var payload pkg.RpmdbMetadata
|
||||||
|
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Metadata = payload
|
||||||
|
case pkg.DpkgMetadataType:
|
||||||
|
var payload pkg.DpkgMetadata
|
||||||
|
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Metadata = payload
|
||||||
|
case pkg.JavaMetadataType:
|
||||||
|
var payload pkg.JavaMetadata
|
||||||
|
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Metadata = payload
|
||||||
|
case pkg.RustCargoPackageMetadataType:
|
||||||
|
var payload pkg.CargoPackageMetadata
|
||||||
|
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Metadata = payload
|
||||||
|
case pkg.GemMetadataType:
|
||||||
|
var payload pkg.GemMetadata
|
||||||
|
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Metadata = payload
|
||||||
|
case pkg.KbPackageMetadataType:
|
||||||
|
var payload pkg.KbPackageMetadata
|
||||||
|
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Metadata = payload
|
||||||
|
case pkg.PythonPackageMetadataType:
|
||||||
|
var payload pkg.PythonPackageMetadata
|
||||||
|
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Metadata = payload
|
||||||
|
case pkg.NpmPackageJSONMetadataType:
|
||||||
|
var payload pkg.NpmPackageJSONMetadata
|
||||||
|
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Metadata = payload
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
8
internal/formats/syftjson/model/relationship.go
Normal file
8
internal/formats/syftjson/model/relationship.go
Normal file
@ -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"`
|
||||||
|
}
|
||||||
45
internal/formats/syftjson/model/source.go
Normal file
45
internal/formats/syftjson/model/source.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Source object represents the thing that was cataloged
|
||||||
|
type Source struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Target interface{} `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourceUnpacker is used to unmarshal Source objects
|
||||||
|
type sourceUnpacker struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Target json.RawMessage `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON populates a source object from JSON bytes.
|
||||||
|
func (s *Source) UnmarshalJSON(b []byte) error {
|
||||||
|
var unpacker sourceUnpacker
|
||||||
|
if err := json.Unmarshal(b, &unpacker); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Type = unpacker.Type
|
||||||
|
|
||||||
|
switch s.Type {
|
||||||
|
case "directory":
|
||||||
|
s.Target = string(unpacker.Target[:])
|
||||||
|
case "image":
|
||||||
|
var payload source.ImageMetadata
|
||||||
|
if err := json.Unmarshal(unpacker.Target, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Target = payload
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported package metadata type: %+v", s.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
# Note: changes to this file will result in updating several test values. Consider making a new image fixture instead of editing this one.
|
||||||
|
FROM scratch
|
||||||
|
ADD file-1.txt /somefile-1.txt
|
||||||
|
ADD file-2.txt /somefile-2.txt
|
||||||
@ -0,0 +1 @@
|
|||||||
|
this file has contents
|
||||||
@ -0,0 +1 @@
|
|||||||
|
file-2 contents!
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
|
"name": "/some/path",
|
||||||
|
"spdxVersion": "SPDX-2.2",
|
||||||
|
"creationInfo": {
|
||||||
|
"created": "2021-09-16T20:44:35.198887Z",
|
||||||
|
"creators": [
|
||||||
|
"Organization: Anchore, Inc",
|
||||||
|
"Tool: syft-[not provided]"
|
||||||
|
],
|
||||||
|
"licenseListVersion": "3.14"
|
||||||
|
},
|
||||||
|
"dataLicense": "CC0-1.0",
|
||||||
|
"documentNamespace": "https://anchore.com/syft/image/",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
|
||||||
|
"name": "package-1",
|
||||||
|
"licenseConcluded": "MIT",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||||
|
"referenceType": "cpe23Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE_MANAGER",
|
||||||
|
"referenceLocator": "a-purl-2",
|
||||||
|
"referenceType": "purl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filesAnalyzed": false,
|
||||||
|
"hasFiles": [
|
||||||
|
"SPDXRef-File-package-1-04cd22424378dcd6c77fce08beb52493b5494a60ea5e1f9bdf9b16dc0cacffe9"
|
||||||
|
],
|
||||||
|
"licenseDeclared": "MIT",
|
||||||
|
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
|
||||||
|
"versionInfo": "1.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-Package-deb-package-2-2.0.1",
|
||||||
|
"name": "package-2",
|
||||||
|
"licenseConcluded": "NONE",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||||
|
"referenceType": "cpe23Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE_MANAGER",
|
||||||
|
"referenceLocator": "a-purl-2",
|
||||||
|
"referenceType": "purl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filesAnalyzed": false,
|
||||||
|
"licenseDeclared": "NONE",
|
||||||
|
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
|
||||||
|
"versionInfo": "2.0.1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-File-package-1-04cd22424378dcd6c77fce08beb52493b5494a60ea5e1f9bdf9b16dc0cacffe9",
|
||||||
|
"name": "foo",
|
||||||
|
"licenseConcluded": "",
|
||||||
|
"fileName": "/some/path/pkg1/depedencies/foo"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"spdxElementId": "SPDXRef-Package-python-package-1-1.0.1",
|
||||||
|
"relationshipType": "CONTAINS",
|
||||||
|
"relatedSpdxElement": "SPDXRef-File-package-1-04cd22424378dcd6c77fce08beb52493b5494a60ea5e1f9bdf9b16dc0cacffe9"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
|
"name": "user-image-input",
|
||||||
|
"spdxVersion": "SPDX-2.2",
|
||||||
|
"creationInfo": {
|
||||||
|
"created": "2021-09-16T20:44:35.203911Z",
|
||||||
|
"creators": [
|
||||||
|
"Organization: Anchore, Inc",
|
||||||
|
"Tool: syft-[not provided]"
|
||||||
|
],
|
||||||
|
"licenseListVersion": "3.14"
|
||||||
|
},
|
||||||
|
"dataLicense": "CC0-1.0",
|
||||||
|
"documentNamespace": "https://anchore.com/syft/image/user-image-input",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
|
||||||
|
"name": "package-1",
|
||||||
|
"licenseConcluded": "MIT",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||||
|
"referenceType": "cpe23Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE_MANAGER",
|
||||||
|
"referenceLocator": "a-purl-1",
|
||||||
|
"referenceType": "purl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filesAnalyzed": false,
|
||||||
|
"licenseDeclared": "MIT",
|
||||||
|
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||||
|
"versionInfo": "1.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-Package-deb-package-2-2.0.1",
|
||||||
|
"name": "package-2",
|
||||||
|
"licenseConcluded": "NONE",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||||
|
"referenceType": "cpe23Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE_MANAGER",
|
||||||
|
"referenceLocator": "a-purl-2",
|
||||||
|
"referenceType": "purl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filesAnalyzed": false,
|
||||||
|
"licenseDeclared": "NONE",
|
||||||
|
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||||
|
"versionInfo": "2.0.1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
144
internal/formats/syftjson/to_format_model.go
Normal file
144
internal/formats/syftjson/to_format_model.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package syftjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal"
|
||||||
|
"github.com/anchore/syft/internal/formats/syftjson/model"
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/version"
|
||||||
|
"github.com/anchore/syft/syft/distro"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
//// NewJSONDocument creates and populates a new JSON document struct from the given cataloging results.
|
||||||
|
//func NewJSONDocument(catalog *pkg.Catalog, srcMetadata source.Metadata, d *distro.Distro, scope source.Scope, configuration interface{}) (Document, error) {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
||||||
|
// TODO: this is export4ed for the use of the power-user command (temp)
|
||||||
|
func ToFormatModel(catalog *pkg.Catalog, srcMetadata *source.Metadata, d *distro.Distro, applicationConfig interface{}) model.Document {
|
||||||
|
src, err := toSourceModel(srcMetadata)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("unable to create syft-json source object: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts, err := toPackageModels(catalog)
|
||||||
|
if err != nil {
|
||||||
|
return model.Document{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.Document{
|
||||||
|
Artifacts: artifacts,
|
||||||
|
ArtifactRelationships: toRelationshipModel(pkg.NewRelationships(catalog)),
|
||||||
|
Source: src,
|
||||||
|
Distro: toDistroModel(d),
|
||||||
|
Descriptor: model.Descriptor{
|
||||||
|
Name: internal.ApplicationName,
|
||||||
|
Version: version.FromBuild().Version,
|
||||||
|
Configuration: applicationConfig,
|
||||||
|
},
|
||||||
|
Schema: model.Schema{
|
||||||
|
Version: internal.JSONSchemaVersion,
|
||||||
|
URL: fmt.Sprintf("https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-%s.json", internal.JSONSchemaVersion),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPackageModels(catalog *pkg.Catalog) ([]model.Package, error) {
|
||||||
|
artifacts := make([]model.Package, 0)
|
||||||
|
if catalog == nil {
|
||||||
|
return artifacts, nil
|
||||||
|
}
|
||||||
|
for _, p := range catalog.Sorted() {
|
||||||
|
art, err := toPackageModel(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
artifacts = append(artifacts, art)
|
||||||
|
}
|
||||||
|
return artifacts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toPackageModel crates a new Package from the given pkg.Package.
|
||||||
|
func toPackageModel(p *pkg.Package) (model.Package, error) {
|
||||||
|
var cpes = make([]string, len(p.CPEs))
|
||||||
|
for i, c := range p.CPEs {
|
||||||
|
cpes[i] = c.BindToFmtString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure collections are never nil for presentation reasons
|
||||||
|
var locations = make([]source.Location, 0)
|
||||||
|
if p.Locations != nil {
|
||||||
|
locations = p.Locations
|
||||||
|
}
|
||||||
|
|
||||||
|
var licenses = make([]string, 0)
|
||||||
|
if p.Licenses != nil {
|
||||||
|
licenses = p.Licenses
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.Package{
|
||||||
|
PackageBasicData: model.PackageBasicData{
|
||||||
|
ID: string(p.ID),
|
||||||
|
Name: p.Name,
|
||||||
|
Version: p.Version,
|
||||||
|
Type: p.Type,
|
||||||
|
FoundBy: p.FoundBy,
|
||||||
|
Locations: locations,
|
||||||
|
Licenses: licenses,
|
||||||
|
Language: p.Language,
|
||||||
|
CPEs: cpes,
|
||||||
|
PURL: p.PURL,
|
||||||
|
},
|
||||||
|
PackageCustomData: model.PackageCustomData{
|
||||||
|
MetadataType: p.MetadataType,
|
||||||
|
Metadata: p.Metadata,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toRelationshipModel(relationships []pkg.Relationship) []model.Relationship {
|
||||||
|
result := make([]model.Relationship, len(relationships))
|
||||||
|
for i, r := range relationships {
|
||||||
|
result[i] = model.Relationship{
|
||||||
|
Parent: string(r.Parent),
|
||||||
|
Child: string(r.Child),
|
||||||
|
Type: string(r.Type),
|
||||||
|
Metadata: r.Metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// toSourceModel creates a new source object to be represented into JSON.
|
||||||
|
func toSourceModel(src *source.Metadata) (model.Source, error) {
|
||||||
|
switch src.Scheme {
|
||||||
|
case source.ImageScheme:
|
||||||
|
return model.Source{
|
||||||
|
Type: "image",
|
||||||
|
Target: src.ImageMetadata,
|
||||||
|
}, nil
|
||||||
|
case source.DirectoryScheme:
|
||||||
|
return model.Source{
|
||||||
|
Type: "directory",
|
||||||
|
Target: src.Path,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return model.Source{}, fmt.Errorf("unsupported source: %q", src.Scheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// toDistroModel creates a struct with the Linux distribution to be represented in JSON.
|
||||||
|
func toDistroModel(d *distro.Distro) model.Distro {
|
||||||
|
if d == nil {
|
||||||
|
return model.Distro{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.Distro{
|
||||||
|
Name: d.Name(),
|
||||||
|
Version: d.FullVersion(),
|
||||||
|
IDLike: d.IDLike,
|
||||||
|
}
|
||||||
|
}
|
||||||
70
internal/formats/syftjson/to_syft_model.go
Normal file
70
internal/formats/syftjson/to_syft_model.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package syftjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anchore/syft/internal/formats/syftjson/model"
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/syft/distro"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toSyftModel(doc model.Document) (*pkg.Catalog, *source.Metadata, *distro.Distro, error) {
|
||||||
|
dist, err := distro.NewDistro(distro.Type(doc.Distro.Name), doc.Distro.Version, doc.Distro.IDLike)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return toSyftCatalog(doc.Artifacts), toSyftSourceMetadata(doc.Source), &dist, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSyftSourceMetadata(s model.Source) *source.Metadata {
|
||||||
|
switch s.Type {
|
||||||
|
case "directory":
|
||||||
|
return &source.Metadata{
|
||||||
|
Scheme: source.DirectoryScheme,
|
||||||
|
Path: s.Target.(string),
|
||||||
|
}
|
||||||
|
case "image":
|
||||||
|
return &source.Metadata{
|
||||||
|
Scheme: source.ImageScheme,
|
||||||
|
ImageMetadata: s.Target.(source.ImageMetadata),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSyftCatalog(pkgs []model.Package) *pkg.Catalog {
|
||||||
|
catalog := pkg.NewCatalog()
|
||||||
|
for _, p := range pkgs {
|
||||||
|
catalog.Add(toSyftPackage(p))
|
||||||
|
}
|
||||||
|
return catalog
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSyftPackage(p model.Package) pkg.Package {
|
||||||
|
var cpes []pkg.CPE
|
||||||
|
for _, c := range p.CPEs {
|
||||||
|
value, err := pkg.NewCPE(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("excluding invalid CPE %q: %v", c, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cpes = append(cpes, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkg.Package{
|
||||||
|
ID: pkg.ID(p.ID),
|
||||||
|
Name: p.Name,
|
||||||
|
Version: p.Version,
|
||||||
|
FoundBy: p.FoundBy,
|
||||||
|
Locations: p.Locations,
|
||||||
|
Licenses: p.Licenses,
|
||||||
|
Language: p.Language,
|
||||||
|
Type: p.Type,
|
||||||
|
CPEs: cpes,
|
||||||
|
PURL: p.PURL,
|
||||||
|
MetadataType: p.MetadataType,
|
||||||
|
Metadata: p.Metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
31
internal/formats/syftjson/validator.go
Normal file
31
internal/formats/syftjson/validator.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package syftjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/formats/syftjson/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validator(reader io.Reader) error {
|
||||||
|
type Document struct {
|
||||||
|
Schema model.Schema `json:"schema"`
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := json.NewDecoder(reader)
|
||||||
|
|
||||||
|
var doc Document
|
||||||
|
err := dec.Decode(&doc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to decode: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// note: we accept al schema versions
|
||||||
|
// TODO: add per-schema version parsing
|
||||||
|
if strings.Contains(doc.Schema.URL, "anchore/syft") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("could not extract syft schema")
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user