mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
[WIP] migrate helper functions for spdx
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
628c2e44a5
commit
ecf11f0e3d
32
internal/formats/common/spdxhelpers/document_name.go
Normal file
32
internal/formats/common/spdxhelpers/document_name.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DocumentName(srcMetadata source.Metadata) string {
|
||||||
|
switch srcMetadata.Scheme {
|
||||||
|
case source.ImageScheme:
|
||||||
|
return cleanSPDXName(srcMetadata.ImageMetadata.UserInput)
|
||||||
|
case source.DirectoryScheme:
|
||||||
|
return cleanSPDXName(srcMetadata.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is this alright?
|
||||||
|
return uuid.Must(uuid.NewRandom()).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanSPDXName(name string) string {
|
||||||
|
// remove # according to specification
|
||||||
|
name = strings.ReplaceAll(name, "#", "-")
|
||||||
|
|
||||||
|
// remove : for url construction
|
||||||
|
name = strings.ReplaceAll(name, ":", "-")
|
||||||
|
|
||||||
|
// clean relative pathing
|
||||||
|
return path.Clean(name)
|
||||||
|
}
|
||||||
29
internal/formats/common/spdxhelpers/document_namespace.go
Normal file
29
internal/formats/common/spdxhelpers/document_namespace.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package spdxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SyftDocumentNamespace = "https://anchore.com/syft"
|
||||||
|
|
||||||
|
func DocumentNamespace(name string, srcMetadata source.Metadata) string {
|
||||||
|
input := "unknown-source-type"
|
||||||
|
switch srcMetadata.Scheme {
|
||||||
|
case source.ImageScheme:
|
||||||
|
input = "image"
|
||||||
|
case source.DirectoryScheme:
|
||||||
|
input = "dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueID := uuid.Must(uuid.NewRandom())
|
||||||
|
identifier := path.Join(input, uniqueID.String())
|
||||||
|
if name != "." {
|
||||||
|
identifier = path.Join(input, fmt.Sprintf("%s-%s", name, uniqueID.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join(SyftDocumentNamespace, identifier)
|
||||||
|
}
|
||||||
22
internal/formats/spdx22json/decoder.go
Normal file
22
internal/formats/spdx22json/decoder.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package spdx22json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
)
|
||||||
|
|
||||||
|
func decoder(reader io.Reader) (*sbom.SBOM, error) {
|
||||||
|
dec := json.NewDecoder(reader)
|
||||||
|
|
||||||
|
var doc model.Document
|
||||||
|
err := dec.Decode(&doc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to decode syft-json: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return toSyftModel(doc)
|
||||||
|
}
|
||||||
46
internal/formats/spdx22json/decoder_test.go
Normal file
46
internal/formats/spdx22json/decoder_test.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package spdx22json
|
||||||
|
|
||||||
|
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"
|
||||||
|
originalSBOM := testutils.ImageInput(t, testImage)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
assert.NoError(t, encoder(&buf, originalSBOM))
|
||||||
|
|
||||||
|
actualSBOM, err := decoder(bytes.NewReader(buf.Bytes()))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for _, d := range deep.Equal(originalSBOM.Source, actualSBOM.Source) {
|
||||||
|
t.Errorf("metadata difference: %+v", d)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualPackages := actualSBOM.Artifacts.PackageCatalog.Sorted()
|
||||||
|
for idx, p := range originalSBOM.Artifacts.PackageCatalog.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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,8 +7,6 @@ import (
|
|||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
)
|
)
|
||||||
|
|
||||||
const anchoreNamespace = "https://anchore.com/syft"
|
|
||||||
|
|
||||||
func encoder(output io.Writer, s sbom.SBOM) error {
|
func encoder(output io.Writer, s sbom.SBOM) error {
|
||||||
doc := toFormatModel(s)
|
doc := toFormatModel(s)
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,6 @@ package spdx22json
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
@ -14,13 +12,11 @@ import (
|
|||||||
"github.com/anchore/syft/internal/spdxlicense"
|
"github.com/anchore/syft/internal/spdxlicense"
|
||||||
"github.com/anchore/syft/internal/version"
|
"github.com/anchore/syft/internal/version"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/source"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// toFormatModel creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
|
// toFormatModel creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
|
||||||
func toFormatModel(s sbom.SBOM) model.Document {
|
func toFormatModel(s sbom.SBOM) model.Document {
|
||||||
name := documentName(s.Source)
|
name := spdxhelpers.DocumentName(s.Source)
|
||||||
packages, files, relationships := extractFromCatalog(s.Artifacts.PackageCatalog)
|
packages, files, relationships := extractFromCatalog(s.Artifacts.PackageCatalog)
|
||||||
|
|
||||||
return model.Document{
|
return model.Document{
|
||||||
@ -39,43 +35,13 @@ func toFormatModel(s sbom.SBOM) model.Document {
|
|||||||
LicenseListVersion: spdxlicense.Version,
|
LicenseListVersion: spdxlicense.Version,
|
||||||
},
|
},
|
||||||
DataLicense: "CC0-1.0",
|
DataLicense: "CC0-1.0",
|
||||||
DocumentNamespace: documentNamespace(name, s.Source),
|
DocumentNamespace: spdxhelpers.DocumentNamespace(name, s.Source),
|
||||||
Packages: packages,
|
Packages: packages,
|
||||||
Files: files,
|
Files: files,
|
||||||
Relationships: relationships,
|
Relationships: relationships,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func documentName(srcMetadata source.Metadata) string {
|
|
||||||
switch srcMetadata.Scheme {
|
|
||||||
case source.ImageScheme:
|
|
||||||
return cleanSPDXName(srcMetadata.ImageMetadata.UserInput)
|
|
||||||
case source.DirectoryScheme:
|
|
||||||
return cleanSPDXName(srcMetadata.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: is this alright?
|
|
||||||
return uuid.Must(uuid.NewRandom()).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func documentNamespace(name string, srcMetadata source.Metadata) string {
|
|
||||||
input := "unknown-source-type"
|
|
||||||
switch srcMetadata.Scheme {
|
|
||||||
case source.ImageScheme:
|
|
||||||
input = "image"
|
|
||||||
case source.DirectoryScheme:
|
|
||||||
input = "dir"
|
|
||||||
}
|
|
||||||
|
|
||||||
uniqueID := uuid.Must(uuid.NewRandom())
|
|
||||||
identifier := path.Join(input, uniqueID.String())
|
|
||||||
if name != "." {
|
|
||||||
identifier = path.Join(input, fmt.Sprintf("%s-%s", name, uniqueID.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.Join(anchoreNamespace, identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractFromCatalog(catalog *pkg.Catalog) ([]model.Package, []model.File, []model.Relationship) {
|
func extractFromCatalog(catalog *pkg.Catalog) ([]model.Package, []model.File, []model.Relationship) {
|
||||||
packages := make([]model.Package, 0)
|
packages := make([]model.Package, 0)
|
||||||
relationships := make([]model.Relationship, 0)
|
relationships := make([]model.Relationship, 0)
|
||||||
@ -115,14 +81,3 @@ func extractFromCatalog(catalog *pkg.Catalog) ([]model.Package, []model.File, []
|
|||||||
|
|
||||||
return packages, files, relationships
|
return packages, files, relationships
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanSPDXName(name string) string {
|
|
||||||
// remove # according to specification
|
|
||||||
name = strings.ReplaceAll(name, "#", "-")
|
|
||||||
|
|
||||||
// remove : for url construction
|
|
||||||
name = strings.ReplaceAll(name, ":", "-")
|
|
||||||
|
|
||||||
// clean relative pathing
|
|
||||||
return path.Clean(name)
|
|
||||||
}
|
|
||||||
|
|||||||
10
internal/formats/spdx22json/to_syft_model.go
Normal file
10
internal/formats/spdx22json/to_syft_model.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package spdx22json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toSyftModel(doc model.Document) (*sbom.SBOM, error) {
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user