From a257fc5ab9b89426bb67dd9511ff299317d1ea02 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Mon, 6 Feb 2023 12:52:53 -0500 Subject: [PATCH 1/6] feat: allow format version specification, e.g. spdx@2.1 Signed-off-by: Keith Zantow --- cmd/syft/cli/attest/attest.go | 14 +-- cmd/syft/cli/options/format.go | 39 -------- cmd/syft/cli/options/packages.go | 6 +- cmd/syft/cli/options/writer.go | 8 +- cmd/syft/cli/poweruser/poweruser.go | 5 +- syft/formats.go | 49 ---------- syft/formats/cyclonedxjson/format.go | 5 +- syft/formats/cyclonedxxml/format.go | 5 +- syft/formats/formats.go | 94 +++++++++++-------- syft/formats/formats_test.go | 80 ++++++++++++---- syft/formats/github/format.go | 5 +- syft/formats/spdxjson/encoder.go | 22 ++++- syft/formats/spdxjson/format.go | 24 ++++- syft/formats/spdxtagvalue/encoder.go | 25 ++++- syft/formats/spdxtagvalue/format.go | 35 ++++++- syft/formats/syftjson/format.go | 5 +- syft/formats/syftjson/to_format_model_test.go | 3 +- syft/formats/table/format.go | 3 +- syft/formats/template/format.go | 19 +++- syft/formats/text/format.go | 3 +- syft/sbom/format.go | 42 ++++++++- syft/sbom/multi_writer_test.go | 2 +- test/cli/all_formats_expressible_test.go | 8 +- test/integration/convert_test.go | 16 ++-- test/integration/encode_decode_cycle_test.go | 3 +- 25 files changed, 309 insertions(+), 211 deletions(-) delete mode 100644 cmd/syft/cli/options/format.go delete mode 100644 syft/formats.go diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index f6bd79d8386..a4d18236ac2 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -22,7 +22,6 @@ import ( "github.com/anchore/syft/syft/event/monitor" "github.com/anchore/syft/syft/formats/syftjson" "github.com/anchore/syft/syft/formats/table" - "github.com/anchore/syft/syft/formats/template" "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" ) @@ -176,16 +175,9 @@ func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <- } func ValidateOutputOptions(app *config.Application) error { - var usesTemplateOutput bool - for _, o := range app.Outputs { - if o == template.ID.String() { - usesTemplateOutput = true - break - } - } - - if usesTemplateOutput && app.OutputTemplatePath == "" { - return fmt.Errorf(`must specify path to template file when using "template" output format`) + err := packages.ValidateOutputOptions(app) + if err != nil { + return err } if len(app.Outputs) > 1 { diff --git a/cmd/syft/cli/options/format.go b/cmd/syft/cli/options/format.go deleted file mode 100644 index e8fabb91432..00000000000 --- a/cmd/syft/cli/options/format.go +++ /dev/null @@ -1,39 +0,0 @@ -package options - -import ( - "github.com/anchore/syft/syft/formats/cyclonedxjson" - "github.com/anchore/syft/syft/formats/cyclonedxxml" - "github.com/anchore/syft/syft/formats/github" - "github.com/anchore/syft/syft/formats/spdxjson" - "github.com/anchore/syft/syft/formats/spdxtagvalue" - "github.com/anchore/syft/syft/formats/syftjson" - "github.com/anchore/syft/syft/formats/table" - "github.com/anchore/syft/syft/formats/text" - "github.com/anchore/syft/syft/sbom" -) - -func FormatAliases(ids ...sbom.FormatID) (aliases []string) { - for _, id := range ids { - switch id { - case syftjson.ID: - aliases = append(aliases, "syft-json") - case text.ID: - aliases = append(aliases, "text") - case table.ID: - aliases = append(aliases, "table") - case spdxjson.ID: - aliases = append(aliases, "spdx-json") - case spdxtagvalue.ID: - aliases = append(aliases, "spdx-tag-value") - case cyclonedxxml.ID: - aliases = append(aliases, "cyclonedx-xml") - case cyclonedxjson.ID: - aliases = append(aliases, "cyclonedx-json") - case github.ID: - aliases = append(aliases, "github", "github-json") - default: - aliases = append(aliases, string(id)) - } - } - return aliases -} diff --git a/cmd/syft/cli/options/packages.go b/cmd/syft/cli/options/packages.go index 0acdcdc2dbb..ec0331e4ff7 100644 --- a/cmd/syft/cli/options/packages.go +++ b/cmd/syft/cli/options/packages.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/anchore/syft/syft" + "github.com/anchore/syft/syft/formats" "github.com/anchore/syft/syft/formats/table" "github.com/anchore/syft/syft/pkg/cataloger" "github.com/anchore/syft/syft/source" @@ -30,8 +30,8 @@ func (o *PackagesOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { cmd.Flags().StringVarP(&o.Scope, "scope", "s", cataloger.DefaultSearchConfig().Scope.String(), fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes)) - cmd.Flags().StringArrayVarP(&o.Output, "output", "o", FormatAliases(table.ID), - fmt.Sprintf("report output format, options=%v", FormatAliases(syft.FormatIDs()...))) + cmd.Flags().StringArrayVarP(&o.Output, "output", "o", []string{string(table.ID)}, + fmt.Sprintf("report output format, options=%v", formats.AllIDs())) cmd.Flags().StringVarP(&o.File, "file", "", "", "file to write the default report output to (default is STDOUT)") diff --git a/cmd/syft/cli/options/writer.go b/cmd/syft/cli/options/writer.go index d93e1db037e..a2e0e37e953 100644 --- a/cmd/syft/cli/options/writer.go +++ b/cmd/syft/cli/options/writer.go @@ -6,7 +6,7 @@ import ( "github.com/hashicorp/go-multierror" - "github.com/anchore/syft/syft" + "github.com/anchore/syft/syft/formats" "github.com/anchore/syft/syft/formats/table" "github.com/anchore/syft/syft/formats/template" "github.com/anchore/syft/syft/sbom" @@ -32,7 +32,7 @@ func MakeWriter(outputs []string, defaultFile, templateFilePath string) (sbom.Wr func parseOutputs(outputs []string, defaultFile, templateFilePath string) (out []sbom.WriterOption, errs error) { // always should have one option -- we generally get the default of "table", but just make sure if len(outputs) == 0 { - outputs = append(outputs, string(table.ID)) + outputs = append(outputs, table.ID.String()) } for _, name := range outputs { @@ -52,9 +52,9 @@ func parseOutputs(outputs []string, defaultFile, templateFilePath string) (out [ file = parts[1] } - format := syft.FormatByName(name) + format := formats.ByName(name) if format == nil { - errs = multierror.Append(errs, fmt.Errorf(`unsupported output format "%s", supported formats are: %+v`, name, FormatAliases(syft.FormatIDs()...))) + errs = multierror.Append(errs, fmt.Errorf(`unsupported output format "%s", supported formats are: %+v`, name, formats.AllIDs())) continue } diff --git a/cmd/syft/cli/poweruser/poweruser.go b/cmd/syft/cli/poweruser/poweruser.go index d989857b59c..82bc6ff393a 100644 --- a/cmd/syft/cli/poweruser/poweruser.go +++ b/cmd/syft/cli/poweruser/poweruser.go @@ -26,9 +26,10 @@ import ( "github.com/anchore/syft/syft/source" ) -func Run(ctx context.Context, app *config.Application, args []string) error { +func Run(_ context.Context, app *config.Application, args []string) error { + f := syftjson.Format() writer, err := sbom.NewWriter(sbom.WriterOption{ - Format: syftjson.Format(), + Format: f, Path: app.File, }) if err != nil { diff --git a/syft/formats.go b/syft/formats.go deleted file mode 100644 index e3cd5d20862..00000000000 --- a/syft/formats.go +++ /dev/null @@ -1,49 +0,0 @@ -package syft - -import ( - "github.com/anchore/syft/syft/formats" - "github.com/anchore/syft/syft/formats/cyclonedxjson" - "github.com/anchore/syft/syft/formats/cyclonedxxml" - "github.com/anchore/syft/syft/formats/github" - "github.com/anchore/syft/syft/formats/spdxjson" - "github.com/anchore/syft/syft/formats/spdxtagvalue" - "github.com/anchore/syft/syft/formats/syftjson" - "github.com/anchore/syft/syft/formats/table" - "github.com/anchore/syft/syft/formats/template" - "github.com/anchore/syft/syft/formats/text" - "github.com/anchore/syft/syft/sbom" -) - -// these have been exported for the benefit of API users -// TODO: deprecated: now that the formats package has been moved to syft/formats, will be removed in v1.0.0 -const ( - JSONFormatID = syftjson.ID - TextFormatID = text.ID - TableFormatID = table.ID - CycloneDxXMLFormatID = cyclonedxxml.ID - CycloneDxJSONFormatID = cyclonedxjson.ID - GitHubFormatID = github.ID - SPDXTagValueFormatID = spdxtagvalue.ID - SPDXJSONFormatID = spdxjson.ID - TemplateFormatID = template.ID -) - -// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 -func FormatIDs() (ids []sbom.FormatID) { - return formats.IDs() -} - -// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 -func FormatByID(id sbom.FormatID) sbom.Format { - return formats.ByID(id) -} - -// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 -func FormatByName(name string) sbom.Format { - return formats.ByName(name) -} - -// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 -func IdentifyFormat(by []byte) sbom.Format { - return formats.Identify(by) -} diff --git a/syft/formats/cyclonedxjson/format.go b/syft/formats/cyclonedxjson/format.go index 14b3005fded..8c1e2e01643 100644 --- a/syft/formats/cyclonedxjson/format.go +++ b/syft/formats/cyclonedxjson/format.go @@ -7,13 +7,14 @@ import ( "github.com/anchore/syft/syft/sbom" ) -const ID sbom.FormatID = "cyclonedx-1-json" +const ID sbom.FormatID = "cyclonedx-json" func Format() sbom.Format { return sbom.NewFormat( - ID, + sbom.AnyVersion, encoder, cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatJSON), cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatJSON), + ID, ) } diff --git a/syft/formats/cyclonedxxml/format.go b/syft/formats/cyclonedxxml/format.go index de1e57cb0da..7fe53c4f7f1 100644 --- a/syft/formats/cyclonedxxml/format.go +++ b/syft/formats/cyclonedxxml/format.go @@ -7,13 +7,14 @@ import ( "github.com/anchore/syft/syft/sbom" ) -const ID sbom.FormatID = "cyclonedx-1-xml" +const ID sbom.FormatID = "cyclonedx-xml" func Format() sbom.Format { return sbom.NewFormat( - ID, + sbom.AnyVersion, encoder, cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatXML), cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatXML), + ID, "cyclonedx", "cyclone", ) } diff --git a/syft/formats/formats.go b/syft/formats/formats.go index 7275e54a638..e3e96ae4852 100644 --- a/syft/formats/formats.go +++ b/syft/formats/formats.go @@ -5,8 +5,11 @@ import ( "errors" "fmt" "io" + "regexp" "strings" + "golang.org/x/exp/slices" + "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/formats/cyclonedxjson" "github.com/anchore/syft/syft/formats/cyclonedxxml" @@ -26,8 +29,11 @@ func Formats() []sbom.Format { cyclonedxxml.Format(), cyclonedxjson.Format(), github.Format(), - spdxtagvalue.Format(), - spdxjson.Format(), + spdxtagvalue.Format2_1(), + spdxtagvalue.Format2_2(), + spdxtagvalue.Format2_3(), + spdxjson.Format2_2(), + spdxjson.Format2_3(), table.Format(), text.Format(), template.Format(), @@ -47,53 +53,51 @@ func Identify(by []byte) sbom.Format { return nil } +// ByName accepts a name@version string, such as: +// +// spdx-json@2.1 or cyclonedx@2 func ByName(name string) sbom.Format { - cleanName := cleanFormatName(name) - for _, f := range Formats() { - if cleanFormatName(string(f.ID())) == cleanName { - return f - } + parts := strings.SplitN(name, "@", 2) + version := sbom.AnyVersion + if len(parts) > 1 { + version = sbom.FormatVersion(parts[1]) } - - // handle any aliases for any supported format - switch cleanName { - case "json", "syftjson": - return ByID(syftjson.ID) - case "cyclonedx", "cyclone", "cyclonedxxml": - return ByID(cyclonedxxml.ID) - case "cyclonedxjson": - return ByID(cyclonedxjson.ID) - case "github", "githubjson": - return ByID(github.ID) - case "spdx", "spdxtv", "spdxtagvalue": - return ByID(spdxtagvalue.ID) - case "spdxjson": - return ByID(spdxjson.ID) - case "table": - return ByID(table.ID) - case "text": - return ByID(text.ID) - case "template": - ByID(template.ID) - } - - return nil + return ByNameAndVersion(parts[0], string(version)) } -func IDs() (ids []sbom.FormatID) { +func ByNameAndVersion(name string, version string) sbom.Format { + name = cleanFormatName(name) + var mostRecentFormat sbom.Format for _, f := range Formats() { - ids = append(ids, f.ID()) + for _, n := range f.IDs() { + if cleanFormatName(string(n)) == name && versionMatches(f.Version(), version) { + if mostRecentFormat == nil || f.Version() > mostRecentFormat.Version() { + mostRecentFormat = f + } + } + } } - return ids + return mostRecentFormat } -func ByID(id sbom.FormatID) sbom.Format { - for _, f := range Formats() { - if f.ID() == id { - return f - } +func versionMatches(version sbom.FormatVersion, match string) bool { + if version == sbom.AnyVersion || match == string(sbom.AnyVersion) { + return true } - return nil + + dots := strings.Count(match, ".") + if dots == 0 { + match += ".*" + } + match = strings.ReplaceAll(match, ".", "\\.") + match = strings.ReplaceAll(match, "*", ".*") + match = strings.ReplaceAll(match, "\\..*", "(\\..*)*") + match = fmt.Sprintf("^%s$", match) + matcher, err := regexp.Compile(match) + if err != nil { + return false + } + return matcher.MatchString(string(version)) } func cleanFormatName(name string) string { @@ -127,3 +131,13 @@ func Decode(reader io.Reader) (*sbom.SBOM, sbom.Format, error) { s, err := f.Decode(bytes.NewReader(by)) return s, f, err } + +func AllIDs() (ids []sbom.FormatID) { + for _, f := range Formats() { + if slices.Contains(ids, f.ID()) { + continue + } + ids = append(ids, f.ID()) + } + return ids +} diff --git a/syft/formats/formats_test.go b/syft/formats/formats_test.go index c2b847ae833..7bef5200f35 100644 --- a/syft/formats/formats_test.go +++ b/syft/formats/formats_test.go @@ -92,18 +92,6 @@ func TestByName(t *testing.T) { name: "spdxtv", // clean variant want: spdxtagvalue.ID, }, - { - name: "spdx-2-tag-value", // clean variant - want: spdxtagvalue.ID, - }, - { - name: "spdx-2-tagvalue", // clean variant - want: spdxtagvalue.ID, - }, - { - name: "spdx2-tagvalue", // clean variant - want: spdxtagvalue.ID, - }, // SPDX JSON { @@ -111,7 +99,7 @@ func TestByName(t *testing.T) { want: spdxjson.ID, }, { - name: "spdx-2-json", + name: "spdxjson", // clean variant want: spdxjson.ID, }, @@ -121,7 +109,7 @@ func TestByName(t *testing.T) { want: cyclonedxjson.ID, }, { - name: "cyclonedx-1-json", + name: "cyclonedxjson", // clean variant want: cyclonedxjson.ID, }, @@ -135,7 +123,7 @@ func TestByName(t *testing.T) { want: cyclonedxxml.ID, }, { - name: "cyclonedx-1-xml", + name: "cyclonedxxml", // clean variant want: cyclonedxxml.ID, }, @@ -144,7 +132,6 @@ func TestByName(t *testing.T) { name: "table", want: table.ID, }, - { name: "syft-table", want: table.ID, @@ -155,7 +142,6 @@ func TestByName(t *testing.T) { name: "text", want: text.ID, }, - { name: "syft-text", want: text.ID, @@ -166,23 +152,26 @@ func TestByName(t *testing.T) { name: "json", want: syftjson.ID, }, - { name: "syft-json", want: syftjson.ID, }, + { + name: "syftjson", // clean variant + want: syftjson.ID, + }, // GitHub JSON { name: "github", want: github.ID, }, - { name: "github-json", want: github.ID, }, + // Syft template { name: "template", want: template.ID, @@ -200,3 +189,56 @@ func TestByName(t *testing.T) { }) } } + +func Test_versionMatches(t *testing.T) { + tests := []struct { + name string + version string + match string + matches bool + }{ + { + name: "any version matches number", + version: string(sbom.AnyVersion), + match: "6", + matches: true, + }, + { + name: "number matches any version", + version: "6", + match: string(sbom.AnyVersion), + matches: true, + }, + { + name: "same number matches", + version: "3", + match: "3", + matches: true, + }, + { + name: "same major number matches", + version: "3.1", + match: "3", + matches: true, + }, + { + name: "same minor number matches", + version: "3.1", + match: "3.1", + matches: true, + }, + { + name: "different number does not match", + version: "3", + match: "4", + matches: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + matches := versionMatches(sbom.FormatVersion(test.version), test.match) + assert.Equal(t, test.matches, matches) + }) + } +} diff --git a/syft/formats/github/format.go b/syft/formats/github/format.go index fad5dbff1a2..7fdd655b3d6 100644 --- a/syft/formats/github/format.go +++ b/syft/formats/github/format.go @@ -7,11 +7,11 @@ import ( "github.com/anchore/syft/syft/sbom" ) -const ID sbom.FormatID = "github-0-json" +const ID sbom.FormatID = "github-json" func Format() sbom.Format { return sbom.NewFormat( - ID, + sbom.AnyVersion, func(writer io.Writer, sbom sbom.SBOM) error { bom := toGithubModel(&sbom) @@ -25,5 +25,6 @@ func Format() sbom.Format { }, nil, nil, + ID, "github", ) } diff --git a/syft/formats/spdxjson/encoder.go b/syft/formats/spdxjson/encoder.go index a8ca26648d8..ba0f92e1ba5 100644 --- a/syft/formats/spdxjson/encoder.go +++ b/syft/formats/spdxjson/encoder.go @@ -4,11 +4,14 @@ import ( "encoding/json" "io" + "github.com/spdx/tools-golang/convert" + "github.com/spdx/tools-golang/spdx/v2/v2_2" + "github.com/anchore/syft/syft/formats/common/spdxhelpers" "github.com/anchore/syft/syft/sbom" ) -func encoder(output io.Writer, s sbom.SBOM) error { +func encoder2_3(output io.Writer, s sbom.SBOM) error { doc := spdxhelpers.ToFormatModel(s) enc := json.NewEncoder(output) @@ -18,3 +21,20 @@ func encoder(output io.Writer, s sbom.SBOM) error { return enc.Encode(doc) } + +func encoder2_2(output io.Writer, s sbom.SBOM) error { + doc := spdxhelpers.ToFormatModel(s) + + var out v2_2.Document + err := convert.Document(doc, &out) + if err != nil { + return err + } + + enc := json.NewEncoder(output) + // prevent > and < from being escaped in the payload + enc.SetEscapeHTML(false) + enc.SetIndent("", " ") + + return enc.Encode(out) +} diff --git a/syft/formats/spdxjson/format.go b/syft/formats/spdxjson/format.go index 6fc2ad78281..4232cb0534e 100644 --- a/syft/formats/spdxjson/format.go +++ b/syft/formats/spdxjson/format.go @@ -4,14 +4,30 @@ import ( "github.com/anchore/syft/syft/sbom" ) -const ID sbom.FormatID = "spdx-2-json" +const ID sbom.FormatID = "spdx-json" + +var IDs = []sbom.FormatID{ID} // note: this format is LOSSY relative to the syftjson format -func Format() sbom.Format { + +func Format2_2() sbom.Format { return sbom.NewFormat( - ID, - encoder, + "2.2", + encoder2_2, + nil, + nil, + IDs..., + ) +} + +func Format2_3() sbom.Format { + return sbom.NewFormat( + "2.3", + encoder2_3, decoder, validator, + IDs..., ) } + +var Format = Format2_3 diff --git a/syft/formats/spdxtagvalue/encoder.go b/syft/formats/spdxtagvalue/encoder.go index fbfd0447120..5949bfc8d52 100644 --- a/syft/formats/spdxtagvalue/encoder.go +++ b/syft/formats/spdxtagvalue/encoder.go @@ -3,13 +3,36 @@ package spdxtagvalue import ( "io" + "github.com/spdx/tools-golang/convert" + "github.com/spdx/tools-golang/spdx/v2/v2_1" + "github.com/spdx/tools-golang/spdx/v2/v2_2" "github.com/spdx/tools-golang/tagvalue" "github.com/anchore/syft/syft/formats/common/spdxhelpers" "github.com/anchore/syft/syft/sbom" ) -func encoder(output io.Writer, s sbom.SBOM) error { +func encoder2_3(output io.Writer, s sbom.SBOM) error { model := spdxhelpers.ToFormatModel(s) return tagvalue.Write(model, output) } + +func encoder2_2(output io.Writer, s sbom.SBOM) error { + model := spdxhelpers.ToFormatModel(s) + var out v2_2.Document + err := convert.Document(model, &out) + if err != nil { + return err + } + return tagvalue.Write(out, output) +} + +func encoder2_1(output io.Writer, s sbom.SBOM) error { + model := spdxhelpers.ToFormatModel(s) + var out v2_1.Document + err := convert.Document(model, &out) + if err != nil { + return err + } + return tagvalue.Write(out, output) +} diff --git a/syft/formats/spdxtagvalue/format.go b/syft/formats/spdxtagvalue/format.go index 4f17561e609..a21150fc737 100644 --- a/syft/formats/spdxtagvalue/format.go +++ b/syft/formats/spdxtagvalue/format.go @@ -4,14 +4,39 @@ import ( "github.com/anchore/syft/syft/sbom" ) -const ID sbom.FormatID = "spdx-2-tag-value" +const ID sbom.FormatID = "spdx-tag-value" -// note: this format is LOSSY relative to the syftjson formation, which means that decoding and validation is not supported at this time -func Format() sbom.Format { +var IDs = []sbom.FormatID{ID, "spdx", "spdx-tv"} + +// note: this format is LOSSY relative to the syftjson format +func Format2_1() sbom.Format { + return sbom.NewFormat( + "2.1", + encoder2_1, + nil, + nil, + IDs..., + ) +} + +func Format2_2() sbom.Format { return sbom.NewFormat( - ID, - encoder, + "2.2", + encoder2_2, + nil, + nil, + IDs..., + ) +} + +func Format2_3() sbom.Format { + return sbom.NewFormat( + "2.3", + encoder2_3, decoder, validator, + IDs..., ) } + +var Format = Format2_3 diff --git a/syft/formats/syftjson/format.go b/syft/formats/syftjson/format.go index af884e14e65..fe5ffca2167 100644 --- a/syft/formats/syftjson/format.go +++ b/syft/formats/syftjson/format.go @@ -4,13 +4,14 @@ import ( "github.com/anchore/syft/syft/sbom" ) -const ID sbom.FormatID = "syft-6-json" +const ID sbom.FormatID = "syft-json" func Format() sbom.Format { return sbom.NewFormat( - ID, + "6", encoder, decoder, validator, + ID, "json", "syft", ) } diff --git a/syft/formats/syftjson/to_format_model_test.go b/syft/formats/syftjson/to_format_model_test.go index e44db8e779f..f28dc6404a9 100644 --- a/syft/formats/syftjson/to_format_model_test.go +++ b/syft/formats/syftjson/to_format_model_test.go @@ -15,8 +15,7 @@ import ( func Test_SyftJsonID_Compatibility(t *testing.T) { jsonMajorVersion := strings.Split(internal.JSONSchemaVersion, ".")[0] - syftJsonIDVersion := strings.Split(string(ID), "-")[1] - assert.Equal(t, jsonMajorVersion, syftJsonIDVersion) + assert.Equal(t, jsonMajorVersion, string(Format().Version())) } func Test_toSourceModel(t *testing.T) { diff --git a/syft/formats/table/format.go b/syft/formats/table/format.go index 66e60e0ec5e..7d96237261e 100644 --- a/syft/formats/table/format.go +++ b/syft/formats/table/format.go @@ -8,9 +8,10 @@ const ID sbom.FormatID = "syft-table" func Format() sbom.Format { return sbom.NewFormat( - ID, + sbom.AnyVersion, encoder, nil, nil, + ID, "table", ) } diff --git a/syft/formats/template/format.go b/syft/formats/template/format.go index 9fe7ad20a31..3ce135683a7 100644 --- a/syft/formats/template/format.go +++ b/syft/formats/template/format.go @@ -1,6 +1,7 @@ package template import ( + "fmt" "io" "github.com/anchore/syft/syft/formats/syftjson" @@ -23,7 +24,19 @@ func (f OutputFormat) ID() sbom.FormatID { return ID } -func (f OutputFormat) Decode(reader io.Reader) (*sbom.SBOM, error) { +func (f OutputFormat) IDs() []sbom.FormatID { + return []sbom.FormatID{ID} +} + +func (f OutputFormat) Version() sbom.FormatVersion { + return sbom.AnyVersion +} + +func (f OutputFormat) String() string { + return fmt.Sprintf("template: " + f.templateFilePath) +} + +func (f OutputFormat) Decode(_ io.Reader) (*sbom.SBOM, error) { return nil, sbom.ErrDecodingNotSupported } @@ -37,7 +50,7 @@ func (f OutputFormat) Encode(output io.Writer, s sbom.SBOM) error { return tmpl.Execute(output, doc) } -func (f OutputFormat) Validate(reader io.Reader) error { +func (f OutputFormat) Validate(_ io.Reader) error { return sbom.ErrValidationNotSupported } @@ -45,3 +58,5 @@ func (f OutputFormat) Validate(reader io.Reader) error { func (f *OutputFormat) SetTemplatePath(filePath string) { f.templateFilePath = filePath } + +var _ sbom.Format = (*OutputFormat)(nil) diff --git a/syft/formats/text/format.go b/syft/formats/text/format.go index e8e5e98b553..387bbd52a8c 100644 --- a/syft/formats/text/format.go +++ b/syft/formats/text/format.go @@ -8,9 +8,10 @@ const ID sbom.FormatID = "syft-text" func Format() sbom.Format { return sbom.NewFormat( - ID, + sbom.AnyVersion, encoder, nil, nil, + ID, "text", ) } diff --git a/syft/sbom/format.go b/syft/sbom/format.go index 13cfa784899..399ff98ccf1 100644 --- a/syft/sbom/format.go +++ b/syft/sbom/format.go @@ -2,6 +2,7 @@ package sbom import ( "errors" + "fmt" "io" ) @@ -18,20 +19,48 @@ func (f FormatID) String() string { return string(f) } +type FormatVersion string + +// String returns a string representation of the FormatVersion. +func (f FormatVersion) String() string { + return string(f) +} + +const AnyVersion FormatVersion = "" + type Format interface { ID() FormatID + IDs() []FormatID + Version() FormatVersion Encode(io.Writer, SBOM) error Decode(io.Reader) (*SBOM, error) Validate(io.Reader) error + String() string } type format struct { - id FormatID + ids []FormatID + version FormatVersion encoder Encoder decoder Decoder validator Validator } +func (f format) IDs() []FormatID { + return f.ids +} + +func (f format) Version() FormatVersion { + return f.version +} + +func (f format) String() string { + if f.version == AnyVersion { + return f.ID().String() + } + return fmt.Sprintf("%s@%s", f.ID(), f.version) +} + // Decoder is a function that can convert an SBOM document of a specific format from a reader into Syft native objects. type Decoder func(reader io.Reader) (*SBOM, error) @@ -47,9 +76,10 @@ type Encoder func(io.Writer, SBOM) error // really represent a different format that also uses json) type Validator func(reader io.Reader) error -func NewFormat(id FormatID, encoder Encoder, decoder Decoder, validator Validator) Format { - return &format{ - id: id, +func NewFormat(version FormatVersion, encoder Encoder, decoder Decoder, validator Validator, ids ...FormatID) Format { + return format{ + ids: ids, + version: version, encoder: encoder, decoder: decoder, validator: validator, @@ -57,7 +87,7 @@ func NewFormat(id FormatID, encoder Encoder, decoder Decoder, validator Validato } func (f format) ID() FormatID { - return f.id + return f.ids[0] } func (f format) Encode(output io.Writer, s SBOM) error { @@ -81,3 +111,5 @@ func (f format) Validate(reader io.Reader) error { return f.validator(reader) } + +var _ Format = (*format)(nil) diff --git a/syft/sbom/multi_writer_test.go b/syft/sbom/multi_writer_test.go index be61c26f7b5..b46e852fc48 100644 --- a/syft/sbom/multi_writer_test.go +++ b/syft/sbom/multi_writer_test.go @@ -15,7 +15,7 @@ func dummyEncoder(io.Writer, SBOM) error { } func dummyFormat(name string) Format { - return NewFormat(FormatID(name), dummyEncoder, nil, nil) + return NewFormat(AnyVersion, dummyEncoder, nil, nil, FormatID(name)) } type writerConfig struct { diff --git a/test/cli/all_formats_expressible_test.go b/test/cli/all_formats_expressible_test.go index 7a70da16c3a..1cc8e9e885b 100644 --- a/test/cli/all_formats_expressible_test.go +++ b/test/cli/all_formats_expressible_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/anchore/syft/syft" + "github.com/anchore/syft/syft/formats" "github.com/anchore/syft/syft/formats/template" ) @@ -21,9 +21,9 @@ func TestAllFormatsExpressible(t *testing.T) { }, assertSuccessfulReturnCode, } - formats := syft.FormatIDs() - require.NotEmpty(t, formats) - for _, o := range formats { + formatNames := formats.AllIDs() + require.NotEmpty(t, formatNames) + for _, o := range formatNames { t.Run(fmt.Sprintf("format:%s", o), func(t *testing.T) { args := []string{"dir:./test-fixtures/image-pkg-coverage", "-o", string(o)} if o == template.ID { diff --git a/test/integration/convert_test.go b/test/integration/convert_test.go index 00f3f6e6a62..6b77de9de4c 100644 --- a/test/integration/convert_test.go +++ b/test/integration/convert_test.go @@ -10,7 +10,7 @@ import ( "github.com/anchore/syft/cmd/syft/cli/convert" "github.com/anchore/syft/internal/config" - "github.com/anchore/syft/syft" + "github.com/anchore/syft/syft/formats" "github.com/anchore/syft/syft/formats/cyclonedxjson" "github.com/anchore/syft/syft/formats/cyclonedxxml" "github.com/anchore/syft/syft/formats/spdxjson" @@ -38,15 +38,15 @@ func TestConvertCmd(t *testing.T) { for _, format := range convertibleFormats { t.Run(format.ID().String(), func(t *testing.T) { sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, nil) - format := syft.FormatByID(syftjson.ID) + syftFormat := syftjson.Format() - f, err := ioutil.TempFile("", "test-convert-sbom-") + file, err := ioutil.TempFile("", "test-convert-sbom-") require.NoError(t, err) defer func() { - os.Remove(f.Name()) + os.Remove(file.Name()) }() - err = format.Encode(f, sbom) + err = syftFormat.Encode(file, sbom) require.NoError(t, err) ctx := context.Background() @@ -59,12 +59,12 @@ func TestConvertCmd(t *testing.T) { os.Stdout = rescue }() - err = convert.Run(ctx, app, []string{f.Name()}) + err = convert.Run(ctx, app, []string{file.Name()}) require.NoError(t, err) - file, err := ioutil.ReadFile(f.Name()) + contents, err := ioutil.ReadFile(file.Name()) require.NoError(t, err) - formatFound := syft.IdentifyFormat(file) + formatFound := formats.Identify(contents) if format.ID() == table.ID { require.Nil(t, formatFound) return diff --git a/test/integration/encode_decode_cycle_test.go b/test/integration/encode_decode_cycle_test.go index 8f47e10ce8c..3385d430ebe 100644 --- a/test/integration/encode_decode_cycle_test.go +++ b/test/integration/encode_decode_cycle_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/syft/syft" + "github.com/anchore/syft/syft/formats" "github.com/anchore/syft/syft/formats/cyclonedxjson" "github.com/anchore/syft/syft/formats/cyclonedxxml" "github.com/anchore/syft/syft/formats/syftjson" @@ -68,7 +69,7 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) { for _, image := range images { originalSBOM, _ := catalogFixtureImage(t, image, source.SquashedScope, nil) - format := syft.FormatByID(test.formatOption) + format := formats.ByName(string(test.formatOption)) require.NotNil(t, format) by1, err := syft.Encode(originalSBOM, format) From e873b3aaa76cf4d506c35da3f71647a1181eb74a Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Mon, 6 Feb 2023 13:50:25 -0500 Subject: [PATCH 2/6] chore: all spdx formats include decoder and validator Signed-off-by: Keith Zantow --- syft/formats/spdxjson/format.go | 4 ++-- syft/formats/spdxtagvalue/format.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/syft/formats/spdxjson/format.go b/syft/formats/spdxjson/format.go index 4232cb0534e..26d543e45a5 100644 --- a/syft/formats/spdxjson/format.go +++ b/syft/formats/spdxjson/format.go @@ -14,8 +14,8 @@ func Format2_2() sbom.Format { return sbom.NewFormat( "2.2", encoder2_2, - nil, - nil, + decoder, + validator, IDs..., ) } diff --git a/syft/formats/spdxtagvalue/format.go b/syft/formats/spdxtagvalue/format.go index a21150fc737..46db72d6fea 100644 --- a/syft/formats/spdxtagvalue/format.go +++ b/syft/formats/spdxtagvalue/format.go @@ -13,8 +13,8 @@ func Format2_1() sbom.Format { return sbom.NewFormat( "2.1", encoder2_1, - nil, - nil, + decoder, + validator, IDs..., ) } @@ -23,8 +23,8 @@ func Format2_2() sbom.Format { return sbom.NewFormat( "2.2", encoder2_2, - nil, - nil, + decoder, + validator, IDs..., ) } From b89124e63858dd60707670948d5db111b2c15c14 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Tue, 7 Feb 2023 09:25:27 -0500 Subject: [PATCH 3/6] chore: update convert_test Signed-off-by: Keith Zantow --- cmd/syft/cli/convert/convert.go | 4 +- syft/encode_decode.go | 18 ------ test/integration/convert_test.go | 65 ++++++++++++++------ test/integration/encode_decode_cycle_test.go | 7 +-- 4 files changed, 50 insertions(+), 44 deletions(-) delete mode 100644 syft/encode_decode.go diff --git a/cmd/syft/cli/convert/convert.go b/cmd/syft/cli/convert/convert.go index d0c23725e94..20817033772 100644 --- a/cmd/syft/cli/convert/convert.go +++ b/cmd/syft/cli/convert/convert.go @@ -8,7 +8,7 @@ import ( "github.com/anchore/syft/cmd/syft/cli/options" "github.com/anchore/syft/internal/config" "github.com/anchore/syft/internal/log" - "github.com/anchore/syft/syft" + "github.com/anchore/syft/syft/formats" ) func Run(_ context.Context, app *config.Application, args []string) error { @@ -34,7 +34,7 @@ func Run(_ context.Context, app *config.Application, args []string) error { _ = f.Close() }() - sbom, _, err := syft.Decode(f) + sbom, _, err := formats.Decode(f) if err != nil { return fmt.Errorf("failed to decode SBOM: %w", err) } diff --git a/syft/encode_decode.go b/syft/encode_decode.go deleted file mode 100644 index b06fb801eb5..00000000000 --- a/syft/encode_decode.go +++ /dev/null @@ -1,18 +0,0 @@ -package syft - -import ( - "io" - - "github.com/anchore/syft/syft/formats" - "github.com/anchore/syft/syft/sbom" -) - -// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 -func Encode(s sbom.SBOM, f sbom.Format) ([]byte, error) { - return formats.Encode(s, f) -} - -// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 -func Decode(reader io.Reader) (*sbom.SBOM, sbom.Format, error) { - return formats.Decode(reader) -} diff --git a/test/integration/convert_test.go b/test/integration/convert_test.go index 6b77de9de4c..d20cab27bd3 100644 --- a/test/integration/convert_test.go +++ b/test/integration/convert_test.go @@ -2,7 +2,7 @@ package integration import ( "context" - "io/ioutil" + "fmt" "os" "testing" @@ -21,36 +21,61 @@ import ( "github.com/anchore/syft/syft/source" ) -var convertibleFormats = []sbom.Format{ - syftjson.Format(), - spdxjson.Format(), - spdxtagvalue.Format(), - cyclonedxjson.Format(), - cyclonedxxml.Format(), -} - // TestConvertCmd tests if the converted SBOM is a valid document according // to spec. // TODO: This test can, but currently does not, check the converted SBOM content. It // might be useful to do that in the future, once we gather a better understanding of // what users expect from the convert command. func TestConvertCmd(t *testing.T) { - for _, format := range convertibleFormats { - t.Run(format.ID().String(), func(t *testing.T) { - sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, nil) + tests := []struct { + name string + format sbom.Format + }{ + { + name: "syft-json", + format: syftjson.Format(), + }, + { + name: "spdx-json", + format: spdxjson.Format(), + }, + { + name: "spdx-tag-value", + format: spdxtagvalue.Format(), + }, + { + name: "cyclonedx-json", + format: cyclonedxjson.Format(), + }, + { + name: "cyclonedx-xml", + format: cyclonedxxml.Format(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + syftSbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, nil) syftFormat := syftjson.Format() - file, err := ioutil.TempFile("", "test-convert-sbom-") + syftFile, err := os.CreateTemp("", "test-convert-sbom-") require.NoError(t, err) defer func() { - os.Remove(file.Name()) + _ = os.Remove(syftFile.Name()) }() - err = syftFormat.Encode(file, sbom) + err = syftFormat.Encode(syftFile, syftSbom) + require.NoError(t, err) + + formatFile, err := os.CreateTemp("", "test-convert-sbom-") require.NoError(t, err) + defer func() { + _ = os.Remove(syftFile.Name()) + }() ctx := context.Background() - app := &config.Application{Outputs: []string{format.ID().String()}} + app := &config.Application{ + Outputs: []string{fmt.Sprintf("%s=%s", test.format.ID().String(), formatFile.Name())}, + } // stdout reduction of test noise rescue := os.Stdout // keep backup of the real stdout @@ -59,17 +84,17 @@ func TestConvertCmd(t *testing.T) { os.Stdout = rescue }() - err = convert.Run(ctx, app, []string{file.Name()}) + err = convert.Run(ctx, app, []string{syftFile.Name()}) require.NoError(t, err) - contents, err := ioutil.ReadFile(file.Name()) + contents, err := os.ReadFile(formatFile.Name()) require.NoError(t, err) formatFound := formats.Identify(contents) - if format.ID() == table.ID { + if test.format.ID() == table.ID { require.Nil(t, formatFound) return } - require.Equal(t, format.ID(), formatFound.ID()) + require.Equal(t, test.format.ID(), formatFound.ID()) }) } } diff --git a/test/integration/encode_decode_cycle_test.go b/test/integration/encode_decode_cycle_test.go index 3385d430ebe..54304b8ac46 100644 --- a/test/integration/encode_decode_cycle_test.go +++ b/test/integration/encode_decode_cycle_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/anchore/syft/syft" "github.com/anchore/syft/syft/formats" "github.com/anchore/syft/syft/formats/cyclonedxjson" "github.com/anchore/syft/syft/formats/cyclonedxxml" @@ -72,14 +71,14 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) { format := formats.ByName(string(test.formatOption)) require.NotNil(t, format) - by1, err := syft.Encode(originalSBOM, format) + by1, err := formats.Encode(originalSBOM, format) assert.NoError(t, err) - newSBOM, newFormat, err := syft.Decode(bytes.NewReader(by1)) + newSBOM, newFormat, err := formats.Decode(bytes.NewReader(by1)) assert.NoError(t, err) assert.Equal(t, format.ID(), newFormat.ID()) - by2, err := syft.Encode(*newSBOM, format) + by2, err := formats.Encode(*newSBOM, format) assert.NoError(t, err) if test.redactor != nil { From 1b739c3930d2dadf6172b7bd47dab47f7bdd6a26 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Tue, 7 Feb 2023 09:59:47 -0500 Subject: [PATCH 4/6] chore: restore syft format functions Signed-off-by: Keith Zantow --- syft/encode_decode.go | 18 ++++++++++++++++ syft/formats.go | 49 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 syft/encode_decode.go create mode 100644 syft/formats.go diff --git a/syft/encode_decode.go b/syft/encode_decode.go new file mode 100644 index 00000000000..b06fb801eb5 --- /dev/null +++ b/syft/encode_decode.go @@ -0,0 +1,18 @@ +package syft + +import ( + "io" + + "github.com/anchore/syft/syft/formats" + "github.com/anchore/syft/syft/sbom" +) + +// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 +func Encode(s sbom.SBOM, f sbom.Format) ([]byte, error) { + return formats.Encode(s, f) +} + +// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 +func Decode(reader io.Reader) (*sbom.SBOM, sbom.Format, error) { + return formats.Decode(reader) +} diff --git a/syft/formats.go b/syft/formats.go new file mode 100644 index 00000000000..71dc8ebfaa3 --- /dev/null +++ b/syft/formats.go @@ -0,0 +1,49 @@ +package syft + +import ( + "github.com/anchore/syft/syft/formats" + "github.com/anchore/syft/syft/formats/cyclonedxjson" + "github.com/anchore/syft/syft/formats/cyclonedxxml" + "github.com/anchore/syft/syft/formats/github" + "github.com/anchore/syft/syft/formats/spdxjson" + "github.com/anchore/syft/syft/formats/spdxtagvalue" + "github.com/anchore/syft/syft/formats/syftjson" + "github.com/anchore/syft/syft/formats/table" + "github.com/anchore/syft/syft/formats/template" + "github.com/anchore/syft/syft/formats/text" + "github.com/anchore/syft/syft/sbom" +) + +// these have been exported for the benefit of API users +// TODO: deprecated: now that the formats package has been moved to syft/formats, will be removed in v1.0.0 +const ( + JSONFormatID = syftjson.ID + TextFormatID = text.ID + TableFormatID = table.ID + CycloneDxXMLFormatID = cyclonedxxml.ID + CycloneDxJSONFormatID = cyclonedxjson.ID + GitHubFormatID = github.ID + SPDXTagValueFormatID = spdxtagvalue.ID + SPDXJSONFormatID = spdxjson.ID + TemplateFormatID = template.ID +) + +// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 +func FormatIDs() (ids []sbom.FormatID) { + return formats.AllIDs() +} + +// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 +func FormatByID(id sbom.FormatID) sbom.Format { + return formats.ByNameAndVersion(string(id), "") +} + +// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 +func FormatByName(name string) sbom.Format { + return formats.ByName(name) +} + +// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0 +func IdentifyFormat(by []byte) sbom.Format { + return formats.Identify(by) +} From 7f2b36ee87fc4c41c499a6fe87bb806fde9ac503 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Tue, 7 Feb 2023 10:19:38 -0500 Subject: [PATCH 5/6] chore: address PR feedback Signed-off-by: Keith Zantow --- syft/formats/formats.go | 10 +++++----- syft/formats/formats_test.go | 2 +- syft/formats/spdxjson/encoder.go | 14 ++++++-------- syft/formats/template/format.go | 2 +- syft/sbom/format.go | 17 +++++------------ 5 files changed, 18 insertions(+), 27 deletions(-) diff --git a/syft/formats/formats.go b/syft/formats/formats.go index e3e96ae4852..900db713cbd 100644 --- a/syft/formats/formats.go +++ b/syft/formats/formats.go @@ -60,9 +60,9 @@ func ByName(name string) sbom.Format { parts := strings.SplitN(name, "@", 2) version := sbom.AnyVersion if len(parts) > 1 { - version = sbom.FormatVersion(parts[1]) + version = parts[1] } - return ByNameAndVersion(parts[0], string(version)) + return ByNameAndVersion(parts[0], version) } func ByNameAndVersion(name string, version string) sbom.Format { @@ -80,8 +80,8 @@ func ByNameAndVersion(name string, version string) sbom.Format { return mostRecentFormat } -func versionMatches(version sbom.FormatVersion, match string) bool { - if version == sbom.AnyVersion || match == string(sbom.AnyVersion) { +func versionMatches(version string, match string) bool { + if version == sbom.AnyVersion || match == sbom.AnyVersion { return true } @@ -97,7 +97,7 @@ func versionMatches(version sbom.FormatVersion, match string) bool { if err != nil { return false } - return matcher.MatchString(string(version)) + return matcher.MatchString(version) } func cleanFormatName(name string) string { diff --git a/syft/formats/formats_test.go b/syft/formats/formats_test.go index 7bef5200f35..3d591b4cf91 100644 --- a/syft/formats/formats_test.go +++ b/syft/formats/formats_test.go @@ -237,7 +237,7 @@ func Test_versionMatches(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - matches := versionMatches(sbom.FormatVersion(test.version), test.match) + matches := versionMatches(test.version, test.match) assert.Equal(t, test.matches, matches) }) } diff --git a/syft/formats/spdxjson/encoder.go b/syft/formats/spdxjson/encoder.go index ba0f92e1ba5..8a2c89f7262 100644 --- a/syft/formats/spdxjson/encoder.go +++ b/syft/formats/spdxjson/encoder.go @@ -13,13 +13,7 @@ import ( func encoder2_3(output io.Writer, s sbom.SBOM) error { doc := spdxhelpers.ToFormatModel(s) - - enc := json.NewEncoder(output) - // prevent > and < from being escaped in the payload - enc.SetEscapeHTML(false) - enc.SetIndent("", " ") - - return enc.Encode(doc) + return encodeJSON(output, doc) } func encoder2_2(output io.Writer, s sbom.SBOM) error { @@ -31,10 +25,14 @@ func encoder2_2(output io.Writer, s sbom.SBOM) error { return err } + return encodeJSON(output, out) +} + +func encodeJSON(output io.Writer, doc interface{}) error { enc := json.NewEncoder(output) // prevent > and < from being escaped in the payload enc.SetEscapeHTML(false) enc.SetIndent("", " ") - return enc.Encode(out) + return enc.Encode(doc) } diff --git a/syft/formats/template/format.go b/syft/formats/template/format.go index 3ce135683a7..4d6fb0ab28c 100644 --- a/syft/formats/template/format.go +++ b/syft/formats/template/format.go @@ -28,7 +28,7 @@ func (f OutputFormat) IDs() []sbom.FormatID { return []sbom.FormatID{ID} } -func (f OutputFormat) Version() sbom.FormatVersion { +func (f OutputFormat) Version() string { return sbom.AnyVersion } diff --git a/syft/sbom/format.go b/syft/sbom/format.go index 399ff98ccf1..c8fcbf5b27f 100644 --- a/syft/sbom/format.go +++ b/syft/sbom/format.go @@ -19,19 +19,12 @@ func (f FormatID) String() string { return string(f) } -type FormatVersion string - -// String returns a string representation of the FormatVersion. -func (f FormatVersion) String() string { - return string(f) -} - -const AnyVersion FormatVersion = "" +const AnyVersion = "" type Format interface { ID() FormatID IDs() []FormatID - Version() FormatVersion + Version() string Encode(io.Writer, SBOM) error Decode(io.Reader) (*SBOM, error) Validate(io.Reader) error @@ -40,7 +33,7 @@ type Format interface { type format struct { ids []FormatID - version FormatVersion + version string encoder Encoder decoder Decoder validator Validator @@ -50,7 +43,7 @@ func (f format) IDs() []FormatID { return f.ids } -func (f format) Version() FormatVersion { +func (f format) Version() string { return f.version } @@ -76,7 +69,7 @@ type Encoder func(io.Writer, SBOM) error // really represent a different format that also uses json) type Validator func(reader io.Reader) error -func NewFormat(version FormatVersion, encoder Encoder, decoder Decoder, validator Validator, ids ...FormatID) Format { +func NewFormat(version string, encoder Encoder, decoder Decoder, validator Validator, ids ...FormatID) Format { return format{ ids: ids, version: version, From ded5a3f7f6f74bafa0f3b5995a6957c223f09684 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Tue, 7 Feb 2023 10:34:27 -0500 Subject: [PATCH 6/6] chore: address PR feedback Signed-off-by: Keith Zantow --- syft/sbom/format.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syft/sbom/format.go b/syft/sbom/format.go index c8fcbf5b27f..5247845b32e 100644 --- a/syft/sbom/format.go +++ b/syft/sbom/format.go @@ -28,7 +28,7 @@ type Format interface { Encode(io.Writer, SBOM) error Decode(io.Reader) (*SBOM, error) Validate(io.Reader) error - String() string + fmt.Stringer } type format struct {