mirror of
https://github.com/anchore/syft.git
synced 2026-11-02 07:26:34 +01:00
Add "pretty" json configuration and change default behavior to be space-efficient (#2275)
* expose underlying format options Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * remove escape html options and address PR feedback Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * incorporate PR feedback Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix cli test 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:
@@ -15,6 +15,7 @@ func SupportedVersions() []string {
|
||||
|
||||
type EncoderConfig struct {
|
||||
Version string
|
||||
Pretty bool // don't include spaces and newlines; same as jq -c
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
@@ -23,7 +24,7 @@ type encoder struct {
|
||||
}
|
||||
|
||||
func NewFormatEncoderWithConfig(cfg EncoderConfig) (sbom.FormatEncoder, error) {
|
||||
enc, err := cyclonedxutil.NewEncoder(cfg.Version, cyclonedx.BOMFileFormatJSON)
|
||||
enc, err := cyclonedxutil.NewEncoder(cfg.Version, cyclonedx.BOMFileFormatJSON, cfg.Pretty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -36,6 +37,7 @@ func NewFormatEncoderWithConfig(cfg EncoderConfig) (sbom.FormatEncoder, error) {
|
||||
func DefaultEncoderConfig() EncoderConfig {
|
||||
return EncoderConfig{
|
||||
Version: cyclonedxutil.DefaultVersion,
|
||||
Pretty: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,15 @@ package cyclonedxjson
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/format/internal/cyclonedxutil"
|
||||
"github.com/anchore/syft/syft/format/internal/testutil"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
@@ -16,11 +19,68 @@ var updateSnapshot = flag.Bool("update-cyclonedx-json", false, "update the *.gol
|
||||
var updateImage = flag.Bool("update-image", false, "update the golden image used for image encoder testing")
|
||||
|
||||
func getEncoder(t testing.TB) sbom.FormatEncoder {
|
||||
enc, err := NewFormatEncoderWithConfig(DefaultEncoderConfig())
|
||||
cfg := DefaultEncoderConfig()
|
||||
cfg.Pretty = true
|
||||
|
||||
enc, err := NewFormatEncoderWithConfig(cfg)
|
||||
require.NoError(t, err)
|
||||
return enc
|
||||
}
|
||||
|
||||
func TestPrettyOutput(t *testing.T) {
|
||||
run := func(opt bool) string {
|
||||
enc, err := NewFormatEncoderWithConfig(EncoderConfig{
|
||||
Version: cyclonedxutil.DefaultVersion,
|
||||
Pretty: opt,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
dir := t.TempDir()
|
||||
s := testutil.DirectoryInput(t, dir)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = enc.Encode(&buffer, s)
|
||||
require.NoError(t, err)
|
||||
|
||||
return strings.TrimSpace(buffer.String())
|
||||
}
|
||||
|
||||
t.Run("pretty", func(t *testing.T) {
|
||||
actual := run(true)
|
||||
assert.Contains(t, actual, "\n")
|
||||
})
|
||||
|
||||
t.Run("compact", func(t *testing.T) {
|
||||
actual := run(false)
|
||||
assert.NotContains(t, actual, "\n")
|
||||
})
|
||||
}
|
||||
|
||||
func TestEscapeHTML(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
s := testutil.DirectoryInput(t, dir)
|
||||
s.Artifacts.Packages.Add(pkg.Package{
|
||||
Name: "<html-package>",
|
||||
})
|
||||
|
||||
// by default we do not escape HTML
|
||||
t.Run("default", func(t *testing.T) {
|
||||
cfg := DefaultEncoderConfig()
|
||||
|
||||
enc, err := NewFormatEncoderWithConfig(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = enc.Encode(&buffer, s)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := buffer.String()
|
||||
assert.Contains(t, actual, "<html-package>")
|
||||
assert.NotContains(t, actual, "\\u003chtml-package\\u003e")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestCycloneDxDirectoryEncoder(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
testutil.AssertEncoderAgainstGoldenSnapshot(t,
|
||||
|
||||
@@ -17,6 +17,7 @@ func SupportedVersions() []string {
|
||||
|
||||
type EncoderConfig struct {
|
||||
Version string
|
||||
Pretty bool // set indent to 0
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
@@ -25,7 +26,7 @@ type encoder struct {
|
||||
}
|
||||
|
||||
func NewFormatEncoderWithConfig(cfg EncoderConfig) (sbom.FormatEncoder, error) {
|
||||
enc, err := cyclonedxutil.NewEncoder(cfg.Version, cyclonedx.BOMFileFormatXML)
|
||||
enc, err := cyclonedxutil.NewEncoder(cfg.Version, cyclonedx.BOMFileFormatXML, cfg.Pretty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,6 +39,7 @@ func NewFormatEncoderWithConfig(cfg EncoderConfig) (sbom.FormatEncoder, error) {
|
||||
func DefaultEncoderConfig() EncoderConfig {
|
||||
return EncoderConfig{
|
||||
Version: cyclonedxutil.DefaultVersion,
|
||||
Pretty: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,14 @@ package cyclonedxxml
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/format/internal/cyclonedxutil"
|
||||
"github.com/anchore/syft/syft/format/internal/testutil"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
@@ -16,11 +19,42 @@ var updateSnapshot = flag.Bool("update-cyclonedx-xml", false, "update the *.gold
|
||||
var updateImage = flag.Bool("update-image", false, "update the golden image used for image encoder testing")
|
||||
|
||||
func getEncoder(t testing.TB) sbom.FormatEncoder {
|
||||
enc, err := NewFormatEncoderWithConfig(DefaultEncoderConfig())
|
||||
cfg := DefaultEncoderConfig()
|
||||
cfg.Pretty = true
|
||||
|
||||
enc, err := NewFormatEncoderWithConfig(cfg)
|
||||
require.NoError(t, err)
|
||||
return enc
|
||||
}
|
||||
|
||||
func TestPrettyOutput(t *testing.T) {
|
||||
enc, err := NewFormatEncoderWithConfig(EncoderConfig{
|
||||
Version: cyclonedxutil.DefaultVersion,
|
||||
Pretty: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
dir := t.TempDir()
|
||||
s := testutil.DirectoryInput(t, dir)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = enc.Encode(&buffer, s)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := buffer.String()
|
||||
lines := strings.Split(actual, "\n")
|
||||
require.NotEmpty(t, lines)
|
||||
whitespace := regexp.MustCompile(`^\s+`)
|
||||
for _, line := range lines {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// require a non-whitespace character (tab, space, etc) as the first character of the line
|
||||
require.False(t, whitespace.Match([]byte(line)), "line should not start with whitespace: %q", line)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCycloneDxDirectoryEncoder(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
testutil.AssertEncoderAgainstGoldenSnapshot(t,
|
||||
|
||||
@@ -14,9 +14,10 @@ const DefaultVersion = "1.5"
|
||||
type Encoder struct {
|
||||
version cyclonedx.SpecVersion
|
||||
format cyclonedx.BOMFileFormat
|
||||
pretty bool
|
||||
}
|
||||
|
||||
func NewEncoder(version string, format cyclonedx.BOMFileFormat) (Encoder, error) {
|
||||
func NewEncoder(version string, format cyclonedx.BOMFileFormat, pretty bool) (Encoder, error) {
|
||||
specVersion, err := SpecVersionFromString(version)
|
||||
if err != nil {
|
||||
return Encoder{}, err
|
||||
@@ -24,13 +25,14 @@ func NewEncoder(version string, format cyclonedx.BOMFileFormat) (Encoder, error)
|
||||
return Encoder{
|
||||
version: specVersion,
|
||||
format: format,
|
||||
pretty: pretty,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e Encoder) Encode(writer io.Writer, s sbom.SBOM) error {
|
||||
bom := cyclonedxhelpers.ToFormatModel(s)
|
||||
enc := cyclonedx.NewBOMEncoder(writer, e.format)
|
||||
enc.SetPretty(true)
|
||||
enc.SetPretty(e.pretty)
|
||||
enc.SetEscapeHTML(false)
|
||||
|
||||
return enc.EncodeVersion(bom, e.version)
|
||||
|
||||
@@ -48,12 +48,8 @@ func AssertEncoderAgainstGoldenSnapshot(t *testing.T, cfg EncoderSnapshotTestCon
|
||||
return
|
||||
}
|
||||
|
||||
var expected []byte
|
||||
if cfg.Redactor != nil {
|
||||
actual = cfg.Redactor.Redact(actual)
|
||||
expected = cfg.Redactor.Redact(testutils.GetGoldenFileContents(t))
|
||||
} else {
|
||||
expected = testutils.GetGoldenFileContents(t)
|
||||
}
|
||||
|
||||
if cfg.UpdateSnapshot && cfg.PersistRedactionsInSnapshot {
|
||||
@@ -62,6 +58,13 @@ func AssertEncoderAgainstGoldenSnapshot(t *testing.T, cfg EncoderSnapshotTestCon
|
||||
return
|
||||
}
|
||||
|
||||
var expected []byte
|
||||
if cfg.Redactor != nil {
|
||||
expected = cfg.Redactor.Redact(testutils.GetGoldenFileContents(t))
|
||||
} else {
|
||||
expected = testutils.GetGoldenFileContents(t)
|
||||
}
|
||||
|
||||
if cfg.IsJSON {
|
||||
require.JSONEq(t, string(expected), string(actual))
|
||||
} else {
|
||||
|
||||
@@ -23,6 +23,7 @@ func SupportedVersions() []string {
|
||||
|
||||
type EncoderConfig struct {
|
||||
Version string
|
||||
Pretty bool // don't include spaces and newlines; same as jq -c
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
@@ -38,6 +39,7 @@ func NewFormatEncoderWithConfig(cfg EncoderConfig) (sbom.FormatEncoder, error) {
|
||||
func DefaultEncoderConfig() EncoderConfig {
|
||||
return EncoderConfig{
|
||||
Version: spdxutil.DefaultVersion,
|
||||
Pretty: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,9 +86,12 @@ func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error {
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(writer)
|
||||
// prevent > and < from being escaped in the payload
|
||||
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
if e.cfg.Pretty {
|
||||
enc.SetIndent("", " ")
|
||||
}
|
||||
|
||||
return enc.Encode(encodeDoc)
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ package spdxjson
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/format/internal/spdxutil"
|
||||
"github.com/anchore/syft/syft/format/internal/testutil"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
@@ -16,11 +19,68 @@ var updateSnapshot = flag.Bool("update-spdx-json", false, "update the *.golden f
|
||||
var updateImage = flag.Bool("update-image", false, "update the golden image used for image encoder testing")
|
||||
|
||||
func getEncoder(t testing.TB) sbom.FormatEncoder {
|
||||
enc, err := NewFormatEncoderWithConfig(DefaultEncoderConfig())
|
||||
cfg := DefaultEncoderConfig()
|
||||
cfg.Pretty = true
|
||||
|
||||
enc, err := NewFormatEncoderWithConfig(cfg)
|
||||
require.NoError(t, err)
|
||||
return enc
|
||||
}
|
||||
|
||||
func TestPrettyOutput(t *testing.T) {
|
||||
run := func(opt bool) string {
|
||||
enc, err := NewFormatEncoderWithConfig(EncoderConfig{
|
||||
Version: spdxutil.DefaultVersion,
|
||||
Pretty: opt,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
dir := t.TempDir()
|
||||
s := testutil.DirectoryInput(t, dir)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = enc.Encode(&buffer, s)
|
||||
require.NoError(t, err)
|
||||
|
||||
return strings.TrimSpace(buffer.String())
|
||||
}
|
||||
|
||||
t.Run("pretty", func(t *testing.T) {
|
||||
actual := run(true)
|
||||
assert.Contains(t, actual, "\n")
|
||||
})
|
||||
|
||||
t.Run("compact", func(t *testing.T) {
|
||||
actual := run(false)
|
||||
assert.NotContains(t, actual, "\n")
|
||||
})
|
||||
}
|
||||
|
||||
func TestEscapeHTML(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
s := testutil.DirectoryInput(t, dir)
|
||||
s.Artifacts.Packages.Add(pkg.Package{
|
||||
Name: "<html-package>",
|
||||
})
|
||||
|
||||
// by default we do not escape HTML
|
||||
t.Run("default", func(t *testing.T) {
|
||||
cfg := DefaultEncoderConfig()
|
||||
|
||||
enc, err := NewFormatEncoderWithConfig(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = enc.Encode(&buffer, s)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := buffer.String()
|
||||
assert.Contains(t, actual, "<html-package>")
|
||||
assert.NotContains(t, actual, "\\u003chtml-package\\u003e")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestSPDXJSONDirectoryEncoder(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
testutil.AssertEncoderAgainstGoldenSnapshot(t,
|
||||
|
||||
@@ -14,6 +14,7 @@ const ID sbom.FormatID = "syft-json"
|
||||
|
||||
type EncoderConfig struct {
|
||||
Legacy bool // transform the output to the legacy syft-json format (pre v1.0 changes, enumerated in the README.md)
|
||||
Pretty bool // don't include spaces and newlines; same as jq -c
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
@@ -37,6 +38,7 @@ func NewFormatEncoderWithConfig(cfg EncoderConfig) (sbom.FormatEncoder, error) {
|
||||
func DefaultEncoderConfig() EncoderConfig {
|
||||
return EncoderConfig{
|
||||
Legacy: false,
|
||||
Pretty: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,9 +61,12 @@ func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error {
|
||||
doc := ToFormatModel(s, e.cfg)
|
||||
|
||||
enc := json.NewEncoder(writer)
|
||||
// prevent > and < from being escaped in the payload
|
||||
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
if e.cfg.Pretty {
|
||||
enc.SetIndent("", " ")
|
||||
}
|
||||
|
||||
return enc.Encode(&doc)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package syftjson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
stereoFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -31,12 +36,69 @@ func TestDefaultNameAndVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrettyOutput(t *testing.T) {
|
||||
run := func(opt bool) string {
|
||||
enc, err := NewFormatEncoderWithConfig(EncoderConfig{
|
||||
Pretty: opt,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
dir := t.TempDir()
|
||||
s := testutil.DirectoryInput(t, dir)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = enc.Encode(&buffer, s)
|
||||
require.NoError(t, err)
|
||||
|
||||
return strings.TrimSpace(buffer.String())
|
||||
}
|
||||
|
||||
t.Run("pretty", func(t *testing.T) {
|
||||
actual := run(true)
|
||||
assert.Contains(t, actual, "\n")
|
||||
})
|
||||
|
||||
t.Run("compact", func(t *testing.T) {
|
||||
actual := run(false)
|
||||
assert.NotContains(t, actual, "\n")
|
||||
})
|
||||
}
|
||||
|
||||
func TestEscapeHTML(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
s := testutil.DirectoryInput(t, dir)
|
||||
s.Artifacts.Packages.Add(pkg.Package{
|
||||
Name: "<html-package>",
|
||||
})
|
||||
|
||||
// by default we do not escape HTML
|
||||
t.Run("default", func(t *testing.T) {
|
||||
cfg := DefaultEncoderConfig()
|
||||
|
||||
enc, err := NewFormatEncoderWithConfig(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = enc.Encode(&buffer, s)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := buffer.String()
|
||||
assert.Contains(t, actual, "<html-package>")
|
||||
assert.NotContains(t, actual, "\\u003chtml-package\\u003e")
|
||||
})
|
||||
}
|
||||
|
||||
func TestDirectoryEncoder(t *testing.T) {
|
||||
cfg := DefaultEncoderConfig()
|
||||
cfg.Pretty = true
|
||||
enc, err := NewFormatEncoderWithConfig(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
dir := t.TempDir()
|
||||
testutil.AssertEncoderAgainstGoldenSnapshot(t,
|
||||
testutil.EncoderSnapshotTestConfig{
|
||||
Subject: testutil.DirectoryInput(t, dir),
|
||||
Format: NewFormatEncoder(),
|
||||
Format: enc,
|
||||
UpdateSnapshot: *updateSnapshot,
|
||||
PersistRedactionsInSnapshot: true,
|
||||
IsJSON: true,
|
||||
@@ -46,6 +108,11 @@ func TestDirectoryEncoder(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestImageEncoder(t *testing.T) {
|
||||
cfg := DefaultEncoderConfig()
|
||||
cfg.Pretty = true
|
||||
enc, err := NewFormatEncoderWithConfig(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
testImage := "image-simple"
|
||||
testutil.AssertEncoderAgainstGoldenImageSnapshot(t,
|
||||
testutil.ImageSnapshotTestConfig{
|
||||
@@ -54,7 +121,7 @@ func TestImageEncoder(t *testing.T) {
|
||||
},
|
||||
testutil.EncoderSnapshotTestConfig{
|
||||
Subject: testutil.ImageInput(t, testImage, testutil.FromSnapshot()),
|
||||
Format: NewFormatEncoder(),
|
||||
Format: enc,
|
||||
UpdateSnapshot: *updateSnapshot,
|
||||
PersistRedactionsInSnapshot: true,
|
||||
IsJSON: true,
|
||||
|
||||
Reference in New Issue
Block a user