From 5c8e8622ba481e71aaa7471d68bdc1816a93ad2e Mon Sep 17 00:00:00 2001 From: Sophie Wigmore Date: Tue, 9 Aug 2022 15:10:36 -0400 Subject: [PATCH] Marshal CycloneDX/SPDX SBOM with indentation (#379) * Marshal CycloneDX/SPDX SBOM with indentation * sbom: add assertions that output is pretty-printed * ignore linter failures on CPE field * indent SBOM JSON with spaces from upstream formats Co-authored-by: Frankie G-J --- sbom/formatted_reader.go | 8 ++++++-- sbom/formatted_reader_test.go | 23 +++++++++++++++++++++++ sbom/sbom.go | 2 ++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/sbom/formatted_reader.go b/sbom/formatted_reader.go index 59219181..ba648d98 100644 --- a/sbom/formatted_reader.go +++ b/sbom/formatted_reader.go @@ -77,7 +77,9 @@ func (f *FormattedReader) Read(b []byte) (int, error) { delete(cycloneDXOutput, "serialNumber") - output, err = json.Marshal(cycloneDXOutput) + // Indent with a two spaces, as they do in CycloneDX: + // https://github.com/CycloneDX/cyclonedx-go/blob/429d353cfcdbfedf367f597cbdde2a840ebf29df/encode.go#L44 + output, err = json.MarshalIndent(cycloneDXOutput, "", " ") if err != nil { return 0, fmt.Errorf("failed to modify CycloneDX SBOM for reproducibility: %w", err) } @@ -133,7 +135,9 @@ func (f *FormattedReader) Read(b []byte) (int, error) { spdxOutput["documentNamespace"] = uri.String() } - output, err = json.Marshal(spdxOutput) + // Indent with a single space, as they do in SPDX: + // https://github.com/anchore/syft/blob/1344889766743beb736aafdfb29266910b738fbb/internal/formats/spdx22json/encoder.go#L16 + output, err = json.MarshalIndent(spdxOutput, "", " ") if err != nil { return 0, fmt.Errorf("failed to modify SPDX SBOM for reproducibility: %w", err) } diff --git a/sbom/formatted_reader_test.go b/sbom/formatted_reader_test.go index 3589e100..7901a38f 100644 --- a/sbom/formatted_reader_test.go +++ b/sbom/formatted_reader_test.go @@ -37,6 +37,12 @@ func testFormattedReader(t *testing.T, context spec.G, it spec.S) { format := syft.IdentifyFormat(buffer.Bytes()) Expect(format.ID()).To(Equal(syft.CycloneDxJSONFormatID)) + // Ensures pretty printing + Expect(buffer.String()).To(ContainSubstring(`{ + "bomFormat": "CycloneDX", + "components": [ + {`)) + var cdxOutput cdxOutput err = json.Unmarshal(buffer.Bytes(), &cdxOutput) @@ -71,6 +77,12 @@ func testFormattedReader(t *testing.T, context spec.G, it spec.S) { format := syft.IdentifyFormat(buffer.Bytes()) Expect(format.ID()).To(Equal(syft.CycloneDxJSONFormatID)) + // Ensures pretty printing + Expect(buffer.String()).To(ContainSubstring(`{ + "bomFormat": "CycloneDX", + "components": [ + {`)) + var cdxOutput cdxOutput err = json.Unmarshal(buffer.Bytes(), &cdxOutput) @@ -105,6 +117,11 @@ func testFormattedReader(t *testing.T, context spec.G, it spec.S) { format := syft.IdentifyFormat(buffer.Bytes()) Expect(format.ID()).To(Equal(syft.SPDXJSONFormatID)) + // Ensures pretty printing + Expect(buffer.String()).To(ContainSubstring(`{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": {`)) + var spdxOutput spdxOutput err = json.Unmarshal(buffer.Bytes(), &spdxOutput) @@ -227,6 +244,12 @@ func testFormattedReader(t *testing.T, context spec.G, it spec.S) { _, err := io.Copy(buffer, sbom.NewFormattedReader(bom, sbom.Format(syft2.ID))) Expect(err).NotTo(HaveOccurred()) + // Ensures pretty printing + Expect(buffer.String()).To(ContainSubstring(`{ + "artifacts": [ + { + "id":`)) + var syftOutput syftOutput err = json.Unmarshal(buffer.Bytes(), &syftOutput) diff --git a/sbom/sbom.go b/sbom/sbom.go index 049298ef..964b5711 100644 --- a/sbom/sbom.go +++ b/sbom/sbom.go @@ -74,6 +74,8 @@ func Generate(path string) (SBOM, error) { // GenerateFromDependency returns a populated SBOM given a postal.Dependency // and the directory path where the dependency will be located within the // application image. + +//nolint Ignore SA1019, informed usage of deprecated package func GenerateFromDependency(dependency postal.Dependency, path string) (SBOM, error) { //nolint Ignore SA1019, informed usage of deprecated package