mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 02:26:42 +01:00
feat: add cyclonedx schema version selection (#2123)
--------- Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
parent
5035d9ca1a
commit
3e16c6813f
2
go.mod
2
go.mod
@ -3,7 +3,7 @@ module github.com/anchore/syft
|
||||
go 1.21.0
|
||||
|
||||
require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.1
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.2
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
|
||||
6
go.sum
6
go.sum
@ -57,8 +57,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.1 h1:5w1SxjGm9MTMNTuRbEPyw21ObdbaagTWF/KfF0qHTRE=
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.1/go.mod h1:N/nrdWQI2SIjaACyyDs/u7+ddCkyl/zkNs8xFsHF2Ps=
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.2 h1:kKQ0t1dPOlugSIYVOMiMtFqeXI2wp/f5DBIdfux8gnQ=
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.2/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
@ -685,6 +685,8 @@ github.com/sylabs/sif/v2 v2.11.5 h1:7ssPH3epSonsTrzbS1YxeJ9KuqAN7ISlSM61a7j/mQM=
|
||||
github.com/sylabs/sif/v2 v2.11.5/go.mod h1:GBoZs9LU3e4yJH1dcZ3Akf/jsqYgy5SeguJQC+zd75Y=
|
||||
github.com/sylabs/squashfs v0.6.1 h1:4hgvHnD9JGlYWwT0bPYNt9zaz23mAV3Js+VEgQoRGYQ=
|
||||
github.com/sylabs/squashfs v0.6.1/go.mod h1:ZwpbPCj0ocIvMy2br6KZmix6Gzh6fsGQcCnydMF+Kx8=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# CycloneDX Schemas
|
||||
|
||||
`syft` generates a CycloneDX BOm output. We want to be able to validate the CycloneDX schemas
|
||||
`syft` generates a CycloneDX Bom output. We want to be able to validate the CycloneDX schemas
|
||||
(and dependent schemas) against generated syft output. The best way to do this is with `xmllint`,
|
||||
however, this tool does not know how to deal with references from HTTP, only the local filesystem.
|
||||
For this reason we've included a copy of all schemas needed to validate `syft` output, modified
|
||||
|
||||
@ -9,11 +9,40 @@ import (
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func encoder(output io.Writer, s sbom.SBOM) error {
|
||||
func encoderV1_0(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_0)
|
||||
}
|
||||
|
||||
func encoderV1_1(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_1)
|
||||
}
|
||||
|
||||
func encoderV1_2(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_2)
|
||||
}
|
||||
|
||||
func encoderV1_3(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_3)
|
||||
}
|
||||
|
||||
func encoderV1_4(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_4)
|
||||
}
|
||||
|
||||
func encoderV1_5(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_5)
|
||||
}
|
||||
|
||||
func buildEncoder(output io.Writer, s sbom.SBOM) (cyclonedx.BOMEncoder, *cyclonedx.BOM) {
|
||||
bom := cyclonedxhelpers.ToFormatModel(s)
|
||||
enc := cyclonedx.NewBOMEncoder(output, cyclonedx.BOMFileFormatJSON)
|
||||
enc.SetPretty(true)
|
||||
enc.SetEscapeHTML(false)
|
||||
err := enc.Encode(bom)
|
||||
return err
|
||||
return enc, bom
|
||||
}
|
||||
|
||||
@ -9,10 +9,62 @@ import (
|
||||
|
||||
const ID sbom.FormatID = "cyclonedx-json"
|
||||
|
||||
func Format() sbom.Format {
|
||||
var Format = Format1_4
|
||||
|
||||
func Format1_0() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
sbom.AnyVersion,
|
||||
encoder,
|
||||
cyclonedx.SpecVersion1_0.String(),
|
||||
encoderV1_0,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatJSON),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatJSON),
|
||||
ID,
|
||||
)
|
||||
}
|
||||
|
||||
func Format1_1() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
cyclonedx.SpecVersion1_1.String(),
|
||||
encoderV1_1,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatJSON),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatJSON),
|
||||
ID,
|
||||
)
|
||||
}
|
||||
|
||||
func Format1_2() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
cyclonedx.SpecVersion1_2.String(),
|
||||
encoderV1_2,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatJSON),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatJSON),
|
||||
ID,
|
||||
)
|
||||
}
|
||||
|
||||
func Format1_3() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
cyclonedx.SpecVersion1_3.String(),
|
||||
encoderV1_3,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatJSON),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatJSON),
|
||||
ID,
|
||||
)
|
||||
}
|
||||
|
||||
func Format1_4() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
cyclonedx.SpecVersion1_4.String(),
|
||||
encoderV1_4,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatJSON),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatJSON),
|
||||
ID,
|
||||
)
|
||||
}
|
||||
|
||||
func Format1_5() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
cyclonedx.SpecVersion1_5.String(),
|
||||
encoderV1_5,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatJSON),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatJSON),
|
||||
ID,
|
||||
|
||||
34
syft/formats/cyclonedxjson/format_test.go
Normal file
34
syft/formats/cyclonedxjson/format_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
package cyclonedxjson
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
)
|
||||
|
||||
func TestFormatVersions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedVersion string
|
||||
}{
|
||||
{
|
||||
|
||||
"cyclonedx-json should default to v1.4",
|
||||
cyclonedx.SpecVersion1_4.String(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range tests {
|
||||
c := c
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
sbomFormat := Format()
|
||||
if sbomFormat.ID() != ID {
|
||||
t.Errorf("expected ID %q, got %q", ID, sbomFormat.ID())
|
||||
}
|
||||
|
||||
if sbomFormat.Version() != c.expectedVersion {
|
||||
t.Errorf("expected version %q, got %q", c.expectedVersion, sbomFormat.Version())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -9,11 +9,40 @@ import (
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func encoder(output io.Writer, s sbom.SBOM) error {
|
||||
func encoderV1_0(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_0)
|
||||
}
|
||||
|
||||
func encoderV1_1(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_1)
|
||||
}
|
||||
|
||||
func encoderV1_2(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_2)
|
||||
}
|
||||
|
||||
func encoderV1_3(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_3)
|
||||
}
|
||||
|
||||
func encoderV1_4(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_4)
|
||||
}
|
||||
|
||||
func encoderV1_5(output io.Writer, s sbom.SBOM) error {
|
||||
enc, bom := buildEncoder(output, s)
|
||||
return enc.EncodeVersion(bom, cyclonedx.SpecVersion1_5)
|
||||
}
|
||||
|
||||
func buildEncoder(output io.Writer, s sbom.SBOM) (cyclonedx.BOMEncoder, *cyclonedx.BOM) {
|
||||
bom := cyclonedxhelpers.ToFormatModel(s)
|
||||
enc := cyclonedx.NewBOMEncoder(output, cyclonedx.BOMFileFormatXML)
|
||||
enc.SetPretty(true)
|
||||
|
||||
err := enc.Encode(bom)
|
||||
return err
|
||||
enc.SetEscapeHTML(false)
|
||||
return enc, bom
|
||||
}
|
||||
|
||||
@ -9,10 +9,62 @@ import (
|
||||
|
||||
const ID sbom.FormatID = "cyclonedx-xml"
|
||||
|
||||
func Format() sbom.Format {
|
||||
var Format = Format1_4
|
||||
|
||||
func Format1_0() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
sbom.AnyVersion,
|
||||
encoder,
|
||||
cyclonedx.SpecVersion1_0.String(),
|
||||
encoderV1_0,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatXML),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatXML),
|
||||
ID, "cyclonedx", "cyclone",
|
||||
)
|
||||
}
|
||||
|
||||
func Format1_1() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
cyclonedx.SpecVersion1_1.String(),
|
||||
encoderV1_1,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatXML),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatXML),
|
||||
ID, "cyclonedx", "cyclone",
|
||||
)
|
||||
}
|
||||
|
||||
func Format1_2() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
cyclonedx.SpecVersion1_2.String(),
|
||||
encoderV1_2,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatXML),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatXML),
|
||||
ID, "cyclonedx", "cyclone",
|
||||
)
|
||||
}
|
||||
|
||||
func Format1_3() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
cyclonedx.SpecVersion1_3.String(),
|
||||
encoderV1_3,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatXML),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatXML),
|
||||
ID, "cyclonedx", "cyclone",
|
||||
)
|
||||
}
|
||||
|
||||
func Format1_4() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
cyclonedx.SpecVersion1_4.String(),
|
||||
encoderV1_4,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatXML),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatXML),
|
||||
ID, "cyclonedx", "cyclone",
|
||||
)
|
||||
}
|
||||
|
||||
func Format1_5() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
cyclonedx.SpecVersion1_5.String(),
|
||||
encoderV1_5,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatXML),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatXML),
|
||||
ID, "cyclonedx", "cyclone",
|
||||
|
||||
@ -26,17 +26,27 @@ import (
|
||||
func Formats() []sbom.Format {
|
||||
return []sbom.Format{
|
||||
syftjson.Format(),
|
||||
cyclonedxxml.Format(),
|
||||
cyclonedxjson.Format(),
|
||||
github.Format(),
|
||||
table.Format(),
|
||||
text.Format(),
|
||||
template.Format(),
|
||||
cyclonedxxml.Format1_0(),
|
||||
cyclonedxxml.Format1_1(),
|
||||
cyclonedxxml.Format1_2(),
|
||||
cyclonedxxml.Format1_3(),
|
||||
cyclonedxxml.Format1_4(),
|
||||
cyclonedxxml.Format1_5(),
|
||||
cyclonedxjson.Format1_0(),
|
||||
cyclonedxjson.Format1_1(),
|
||||
cyclonedxjson.Format1_2(),
|
||||
cyclonedxjson.Format1_3(),
|
||||
cyclonedxjson.Format1_4(),
|
||||
cyclonedxjson.Format1_5(),
|
||||
spdxtagvalue.Format2_1(),
|
||||
spdxtagvalue.Format2_2(),
|
||||
spdxtagvalue.Format2_3(),
|
||||
spdxjson.Format2_2(),
|
||||
spdxjson.Format2_3(),
|
||||
table.Format(),
|
||||
text.Format(),
|
||||
template.Format(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +65,7 @@ func Identify(by []byte) sbom.Format {
|
||||
|
||||
// ByName accepts a name@version string, such as:
|
||||
//
|
||||
// spdx-json@2.1 or cyclonedx@2
|
||||
// spdx-json@2.1 or cyclonedx@1.5
|
||||
func ByName(name string) sbom.Format {
|
||||
parts := strings.SplitN(name, "@", 2)
|
||||
version := sbom.AnyVersion
|
||||
@ -71,6 +81,16 @@ func ByNameAndVersion(name string, version string) sbom.Format {
|
||||
for _, f := range Formats() {
|
||||
for _, n := range f.IDs() {
|
||||
if cleanFormatName(string(n)) == name && versionMatches(f.Version(), version) {
|
||||
// if the version is not specified and the format is cyclonedx, then we want to return the most recent version up to 1.4
|
||||
// If more aliases like cdx are added this will not catch those - we want to eventually provide a way for
|
||||
// formats to inform this function what their default version is
|
||||
// TODO: remove this check when 1.5 is stable or default formats are designed. PR below should be merged.
|
||||
// https://github.com/CycloneDX/cyclonedx-go/pull/90
|
||||
if version == sbom.AnyVersion && strings.Contains(string(n), "cyclone") {
|
||||
if f.Version() == "1.5" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if mostRecentFormat == nil || f.Version() > mostRecentFormat.Version() {
|
||||
mostRecentFormat = f
|
||||
}
|
||||
|
||||
@ -70,7 +70,6 @@ func TestFormats_EmptyInput(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestByName(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
want sbom.FormatID
|
||||
|
||||
33
syft/formats/syftjson/format_test.go
Normal file
33
syft/formats/syftjson/format_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package syftjson
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
)
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
}{
|
||||
{
|
||||
name: "default version should use latest internal version",
|
||||
version: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range tests {
|
||||
c := c
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
sbomFormat := Format()
|
||||
if sbomFormat.ID() != ID {
|
||||
t.Errorf("expected ID %q, got %q", ID, sbomFormat.ID())
|
||||
}
|
||||
|
||||
if sbomFormat.Version() != internal.JSONSchemaVersion {
|
||||
t.Errorf("expected version %q, got %q", c.version, sbomFormat.Version())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user