-
Notifications
You must be signed in to change notification settings - Fork 574
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add distribution client Signed-off-by: Alex Goodman <[email protected]> * show message on deprecation or EOL Signed-off-by: Alex Goodman <[email protected]> * rename metadata.json to description.json Signed-off-by: Alex Goodman <[email protected]> --------- Signed-off-by: Alex Goodman <[email protected]>
- Loading branch information
Showing
10 changed files
with
1,342 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package schemaver | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
type SchemaVer string | ||
|
||
func New(model, revision, addition int) SchemaVer { | ||
return SchemaVer(fmt.Sprintf("%d.%d.%d", model, revision, addition)) | ||
} | ||
|
||
func (s SchemaVer) String() string { | ||
return string(s) | ||
} | ||
|
||
func (s SchemaVer) ModelPart() (int, bool) { | ||
v, ok := parseVersionPart(s, 0) | ||
if v == 0 { | ||
ok = false | ||
} | ||
return v, ok | ||
} | ||
|
||
func (s SchemaVer) RevisionPart() (int, bool) { | ||
return parseVersionPart(s, 1) | ||
} | ||
|
||
func (s SchemaVer) AdditionPart() (int, bool) { | ||
return parseVersionPart(s, 2) | ||
} | ||
|
||
func parseVersionPart(s SchemaVer, index int) (int, bool) { | ||
parts := strings.Split(string(s), ".") | ||
if len(parts) <= index { | ||
return 0, false | ||
} | ||
value, err := strconv.Atoi(parts[index]) | ||
if err != nil { | ||
return 0, false | ||
} | ||
return value, true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package schemaver | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestSchemaVer_VersionComponents(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
version SchemaVer | ||
expectedModel int | ||
expectedRevision int | ||
expectedAddition int | ||
}{ | ||
{ | ||
name: "go case", | ||
version: "1.2.3", | ||
expectedModel: 1, | ||
expectedRevision: 2, | ||
expectedAddition: 3, | ||
}, | ||
{ | ||
name: "model only", | ||
version: "1.0.0", | ||
expectedModel: 1, | ||
expectedRevision: 0, | ||
expectedAddition: 0, | ||
}, | ||
{ | ||
name: "invalid model", | ||
version: "0.2.3", | ||
expectedModel: -1, | ||
expectedRevision: 2, | ||
expectedAddition: 3, | ||
}, | ||
{ | ||
name: "invalid version format", | ||
version: "invalid.version", | ||
expectedModel: -1, | ||
expectedRevision: -1, | ||
expectedAddition: -1, | ||
}, | ||
{ | ||
name: "zero version", | ||
version: "0.0.0", | ||
expectedModel: -1, | ||
expectedRevision: 0, | ||
expectedAddition: 0, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
type subject struct { | ||
name string | ||
exp int | ||
fn func() (int, bool) | ||
} | ||
|
||
for _, sub := range []subject{ | ||
{ | ||
name: "model", | ||
exp: tt.expectedModel, | ||
fn: tt.version.ModelPart, | ||
}, | ||
{ | ||
name: "revision", | ||
exp: tt.expectedRevision, | ||
fn: tt.version.RevisionPart, | ||
}, | ||
{ | ||
name: "addition", | ||
exp: tt.expectedAddition, | ||
fn: tt.version.AdditionPart, | ||
}, | ||
} { | ||
t.Run(sub.name, func(t *testing.T) { | ||
act, ok := sub.fn() | ||
|
||
if sub.exp == -1 { | ||
require.False(t, ok, fmt.Sprintf("Expected %s to be invalid", sub.name)) | ||
return | ||
} | ||
require.True(t, ok, fmt.Sprintf("Expected %s to be valid", sub.name)) | ||
assert.Equal(t, sub.exp, act, fmt.Sprintf("Expected %s to be %d, got %d", sub.name, sub.exp, act)) | ||
}) | ||
} | ||
|
||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package v6 | ||
|
||
import ( | ||
"fmt" | ||
"path" | ||
"time" | ||
|
||
"github.com/OneOfOne/xxhash" | ||
"github.com/spf13/afero" | ||
|
||
"github.com/anchore/grype/grype/db/internal/schemaver" | ||
"github.com/anchore/grype/internal/file" | ||
) | ||
|
||
const DescriptionFileName = "description.json" | ||
|
||
type Description struct { | ||
// SchemaVersion is the version of the DB schema | ||
SchemaVersion schemaver.SchemaVer `json:"schemaVersion,omitempty"` | ||
|
||
// Built is the timestamp the database was built | ||
Built Time `json:"built"` | ||
|
||
// Checksum is the self-describing digest of the database file | ||
Checksum string `json:"checksum"` | ||
} | ||
|
||
type Time struct { | ||
time.Time | ||
} | ||
|
||
func (t Time) MarshalJSON() ([]byte, error) { | ||
return []byte(fmt.Sprintf("%q", t.String())), nil | ||
} | ||
|
||
func (t *Time) UnmarshalJSON(data []byte) error { | ||
str := string(data) | ||
if len(str) < 2 || str[0] != '"' || str[len(str)-1] != '"' { | ||
return fmt.Errorf("invalid time format") | ||
} | ||
str = str[1 : len(str)-1] | ||
|
||
parsedTime, err := time.Parse(time.RFC3339, str) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
t.Time = parsedTime.In(time.UTC) | ||
return nil | ||
} | ||
|
||
func (t Time) String() string { | ||
return t.Time.UTC().Round(time.Second).Format(time.RFC3339) | ||
} | ||
|
||
func NewDescriptionFromDir(fs afero.Fs, dir string) (*Description, error) { | ||
// checksum the DB file | ||
dbFilePath := path.Join(dir, VulnerabilityDBFileName) | ||
digest, err := file.HashFile(fs, dbFilePath, xxhash.New64()) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to calculate checksum for DB file (%s): %w", dbFilePath, err) | ||
} | ||
namedDigest := fmt.Sprintf("xxh64:%s", digest) | ||
|
||
// access the DB to get the built time and schema version | ||
r, err := NewReader(Config{ | ||
DBDirPath: dir, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
meta, err := r.GetDBMetadata() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &Description{ | ||
SchemaVersion: schemaver.New(meta.Model, meta.Revision, meta.Addition), | ||
Built: Time{Time: *meta.BuildTimestamp}, | ||
Checksum: namedDigest, | ||
}, nil | ||
} | ||
|
||
func (m Description) String() string { | ||
return fmt.Sprintf("DB(version=%s built=%s checksum=%s)", m.SchemaVersion, m.Built, m.Checksum) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package v6 | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path" | ||
"testing" | ||
"time" | ||
|
||
"github.com/OneOfOne/xxhash" | ||
"github.com/spf13/afero" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/anchore/grype/grype/db/internal/schemaver" | ||
) | ||
|
||
func TestNewDatabaseDescriptionFromDir(t *testing.T) { | ||
tempDir := t.TempDir() | ||
|
||
// make a test DB | ||
s, err := NewWriter(Config{DBDirPath: tempDir}) | ||
require.NoError(t, err) | ||
require.NoError(t, s.SetDBMetadata()) | ||
expected, err := s.GetDBMetadata() | ||
require.NoError(t, err) | ||
require.NoError(t, s.Close()) | ||
|
||
// get the xxhash of the db file | ||
hasher := xxhash.New64() | ||
f, err := os.Open(path.Join(tempDir, VulnerabilityDBFileName)) | ||
require.NoError(t, err) | ||
_, err = io.Copy(hasher, f) | ||
require.NoError(t, err) | ||
require.NoError(t, f.Close()) | ||
expectedHash := fmt.Sprintf("xxh64:%x", hasher.Sum(nil)) | ||
|
||
// run the test subject | ||
description, err := NewDescriptionFromDir(afero.NewOsFs(), tempDir) | ||
require.NoError(t, err) | ||
require.NotNil(t, description) | ||
|
||
// did it work? | ||
assert.Equal(t, Description{ | ||
SchemaVersion: schemaver.New(expected.Model, expected.Revision, expected.Addition), | ||
Built: Time{*expected.BuildTimestamp}, | ||
Checksum: expectedHash, | ||
}, *description) | ||
} | ||
|
||
func TestTime_JSONMarshalling(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
time Time | ||
expected string | ||
}{ | ||
{ | ||
name: "go case", | ||
time: Time{time.Date(2023, 9, 26, 12, 0, 0, 0, time.UTC)}, | ||
expected: `"2023-09-26T12:00:00Z"`, | ||
}, | ||
{ | ||
name: "convert to utc", | ||
time: Time{time.Date(2023, 9, 26, 13, 0, 0, 0, time.FixedZone("UTC+1", 3600))}, | ||
expected: `"2023-09-26T12:00:00Z"`, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
jsonData, err := json.Marshal(tt.time) | ||
require.NoError(t, err) | ||
require.Equal(t, tt.expected, string(jsonData)) | ||
}) | ||
} | ||
} | ||
|
||
func TestTime_JSONUnmarshalling(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
jsonData string | ||
expectedTime Time | ||
expectError require.ErrorAssertionFunc | ||
}{ | ||
{ | ||
name: "use zulu offset", | ||
jsonData: `"2023-09-26T12:00:00Z"`, | ||
expectedTime: Time{time.Date(2023, 9, 26, 12, 0, 0, 0, time.UTC)}, | ||
}, | ||
{ | ||
name: "use tz offset in another timezone", | ||
jsonData: `"2023-09-26T14:00:00+02:00"`, | ||
expectedTime: Time{time.Date(2023, 9, 26, 12, 0, 0, 0, time.UTC)}, | ||
}, | ||
{ | ||
name: "use tz offset that is utc", | ||
jsonData: `"2023-09-26T12:00:00+00:00"`, | ||
expectedTime: Time{time.Date(2023, 9, 26, 12, 0, 0, 0, time.UTC)}, | ||
}, | ||
{ | ||
name: "invalid format", | ||
jsonData: `"invalid-time-format"`, | ||
expectError: require.Error, | ||
}, | ||
{ | ||
name: "invalid json", | ||
jsonData: `invalid`, | ||
expectError: require.Error, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if tt.expectError == nil { | ||
tt.expectError = require.NoError | ||
} | ||
var parsedTime Time | ||
err := json.Unmarshal([]byte(tt.jsonData), &parsedTime) | ||
tt.expectError(t, err) | ||
if err == nil { | ||
assert.Equal(t, tt.expectedTime.Time, parsedTime.Time) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.