feat: add timestamp to json output (#1170) (#1249)

Signed-off-by: James Neate <jamesmneate@gmail.com>
This commit is contained in:
James Neate 2023-04-21 17:18:30 +01:00 committed by GitHub
parent b9fa68e3a9
commit 04c0bc1bcd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 50 additions and 3 deletions

View file

@ -3,6 +3,7 @@ package json
import (
"bytes"
"flag"
"regexp"
"testing"
"github.com/stretchr/testify/assert"
@ -16,6 +17,7 @@ import (
)
var update = flag.Bool("update", false, "update the *.golden files for json presenters")
var timestampRegexp = regexp.MustCompile(`"timestamp":\s*"[^"]+"`)
func TestJsonImgsPresenter(t *testing.T) {
var buffer bytes.Buffer
@ -35,6 +37,8 @@ func TestJsonImgsPresenter(t *testing.T) {
t.Fatal(err)
}
actual := buffer.Bytes()
actual = redact(actual)
if *update {
testutils.UpdateGoldenFileContents(t, actual)
}
@ -66,6 +70,7 @@ func TestJsonDirsPresenter(t *testing.T) {
t.Fatal(err)
}
actual := buffer.Bytes()
actual = redact(actual)
if *update {
testutils.UpdateGoldenFileContents(t, actual)
@ -108,6 +113,8 @@ func TestEmptyJsonPresenter(t *testing.T) {
t.Fatal(err)
}
actual := buffer.Bytes()
actual = redact(actual)
if *update {
testutils.UpdateGoldenFileContents(t, actual)
}
@ -115,4 +122,9 @@ func TestEmptyJsonPresenter(t *testing.T) {
var expected = testutils.GetGoldenFileContents(t)
assert.JSONEq(t, string(expected), string(actual))
}
func redact(content []byte) []byte {
return timestampRegexp.ReplaceAll(content, []byte(`"timestamp":""`))
}

View file

@ -13,6 +13,7 @@
},
"descriptor": {
"name": "grype",
"version": "[not provided]"
"version": "[not provided]",
"timestamp": ""
}
}

View file

@ -145,6 +145,7 @@
},
"descriptor": {
"name": "grype",
"version": "[not provided]"
"version": "[not provided]",
"timestamp": ""
}
}

View file

@ -174,6 +174,7 @@
},
"descriptor": {
"name": "grype",
"version": "[not provided]"
"version": "[not provided]",
"timestamp": ""
}
}

View file

@ -6,4 +6,5 @@ type descriptor struct {
Version string `json:"version"`
Configuration interface{} `json:"configuration,omitempty"`
VulnerabilityDBStatus interface{} `json:"db,omitempty"`
Timestamp string `json:"timestamp"`
}

View file

@ -2,6 +2,7 @@ package models
import (
"fmt"
"time"
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/pkg"
@ -21,6 +22,11 @@ type Document struct {
// NewDocument creates and populates a new Document struct, representing the populated JSON document.
func NewDocument(packages []pkg.Package, context pkg.Context, matches match.Matches, ignoredMatches []match.IgnoredMatch, metadataProvider vulnerability.MetadataProvider, appConfig interface{}, dbStatus interface{}) (Document, error) {
timestamp, timestampErr := time.Now().Local().MarshalText()
if timestampErr != nil {
return Document{}, timestampErr
}
// we must preallocate the findings to ensure the JSON document does not show "null" when no matches are found
var findings = make([]Match, 0)
for _, m := range matches.Sorted() {
@ -75,6 +81,7 @@ func NewDocument(packages []pkg.Package, context pkg.Context, matches match.Matc
Version: version.FromBuild().Version,
Configuration: appConfig,
VulnerabilityDBStatus: dbStatus,
Timestamp: string(timestamp),
},
}, nil
}

View file

@ -2,6 +2,7 @@ package models
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
@ -93,3 +94,26 @@ func TestPackagesAreSorted(t *testing.T) {
assert.Equal(t, []string{"CVE-1999-0001", "CVE-1999-0002", "CVE-1999-0003"}, actualVulnerabilities)
}
func TestTimestampValidFormat(t *testing.T) {
matches := match.NewMatches()
ctx := pkg.Context{
Source: nil,
Distro: nil,
}
doc, err := NewDocument(nil, ctx, matches, nil, nil, nil, nil)
if err != nil {
t.Fatalf("unable to get document: %+v", err)
}
assert.NotEmpty(t, doc.Descriptor.Timestamp)
// Check format is RFC3339 compatible e.g. 2023-04-21T00:22:06.491137+01:00
_, timeErr := time.Parse(time.RFC3339, doc.Descriptor.Timestamp)
if timeErr != nil {
t.Fatalf("unable to parse time: %+v", timeErr)
}
}