database: Use LayerWithContent as Layer
This commit is contained in:
parent
ff9303905b
commit
e160616723
@ -123,7 +123,7 @@ func VulnerabilityWithFixedInFromDatabaseModel(dbVuln database.VulnerabilityWith
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LayerFromDatabaseModel converts database layer to api layer.
|
// LayerFromDatabaseModel converts database layer to api layer.
|
||||||
func LayerFromDatabaseModel(dbLayer database.Layer) *Layer {
|
func LayerFromDatabaseModel(dbLayer database.LayerMetadata) *Layer {
|
||||||
layer := Layer{Hash: dbLayer.Hash}
|
layer := Layer{Hash: dbLayer.Hash}
|
||||||
return &layer
|
return &layer
|
||||||
}
|
}
|
||||||
|
@ -120,22 +120,16 @@ type Session interface {
|
|||||||
// PersistNamespaces inserts a set of namespaces if not in the database.
|
// PersistNamespaces inserts a set of namespaces if not in the database.
|
||||||
PersistNamespaces([]Namespace) error
|
PersistNamespaces([]Namespace) error
|
||||||
|
|
||||||
// PersistLayer creates a layer using the blob Sum hash.
|
// PersistLayer persists a layer's content in the database. The given
|
||||||
PersistLayer(hash string) error
|
|
||||||
|
|
||||||
// PersistLayerContent persists a layer's content in the database. The given
|
|
||||||
// namespaces and features can be partial content of this layer.
|
// namespaces and features can be partial content of this layer.
|
||||||
//
|
//
|
||||||
// The layer, namespaces and features are expected to be already existing
|
// The layer, namespaces and features are expected to be already existing
|
||||||
// in the database.
|
// in the database.
|
||||||
PersistLayerContent(hash string, namespaces []Namespace, features []Feature, processedBy Processors) error
|
PersistLayer(hash string, namespaces []Namespace, features []Feature, processedBy Processors) error
|
||||||
|
|
||||||
// FindLayer retrieves the metadata of a layer.
|
// FindLayer returns a layer with all detected features and
|
||||||
FindLayer(hash string) (layer Layer, found bool, err error)
|
|
||||||
|
|
||||||
// FindLayerWithContent returns a layer with all detected features and
|
|
||||||
// namespaces.
|
// namespaces.
|
||||||
FindLayerWithContent(hash string) (layer LayerWithContent, found bool, err error)
|
FindLayer(hash string) (layer Layer, found bool, err error)
|
||||||
|
|
||||||
// InsertVulnerabilities inserts a set of UNIQUE vulnerabilities with
|
// InsertVulnerabilities inserts a set of UNIQUE vulnerabilities with
|
||||||
// affected features into database, assuming that all vulnerabilities
|
// affected features into database, assuming that all vulnerabilities
|
||||||
|
@ -32,10 +32,8 @@ type MockSession struct {
|
|||||||
FctPersistFeatures func([]Feature) error
|
FctPersistFeatures func([]Feature) error
|
||||||
FctPersistNamespacedFeatures func([]NamespacedFeature) error
|
FctPersistNamespacedFeatures func([]NamespacedFeature) error
|
||||||
FctCacheAffectedNamespacedFeatures func([]NamespacedFeature) error
|
FctCacheAffectedNamespacedFeatures func([]NamespacedFeature) error
|
||||||
FctPersistLayer func(hash string) error
|
FctPersistLayer func(hash string, namespaces []Namespace, features []Feature, processedBy Processors) error
|
||||||
FctPersistLayerContent func(hash string, namespaces []Namespace, features []Feature, processedBy Processors) error
|
|
||||||
FctFindLayer func(name string) (Layer, bool, error)
|
FctFindLayer func(name string) (Layer, bool, error)
|
||||||
FctFindLayerWithContent func(name string) (LayerWithContent, bool, error)
|
|
||||||
FctInsertVulnerabilities func([]VulnerabilityWithAffected) error
|
FctInsertVulnerabilities func([]VulnerabilityWithAffected) error
|
||||||
FctFindVulnerabilities func([]VulnerabilityID) ([]NullableVulnerability, error)
|
FctFindVulnerabilities func([]VulnerabilityID) ([]NullableVulnerability, error)
|
||||||
FctDeleteVulnerabilities func([]VulnerabilityID) error
|
FctDeleteVulnerabilities func([]VulnerabilityID) error
|
||||||
@ -115,16 +113,9 @@ func (ms *MockSession) CacheAffectedNamespacedFeatures(namespacedFeatures []Name
|
|||||||
panic("required mock function not implemented")
|
panic("required mock function not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *MockSession) PersistLayer(layer string) error {
|
func (ms *MockSession) PersistLayer(hash string, namespaces []Namespace, features []Feature, processedBy Processors) error {
|
||||||
if ms.FctPersistLayer != nil {
|
if ms.FctPersistLayer != nil {
|
||||||
return ms.FctPersistLayer(layer)
|
return ms.FctPersistLayer(hash, namespaces, features, processedBy)
|
||||||
}
|
|
||||||
panic("required mock function not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *MockSession) PersistLayerContent(hash string, namespaces []Namespace, features []Feature, processedBy Processors) error {
|
|
||||||
if ms.FctPersistLayerContent != nil {
|
|
||||||
return ms.FctPersistLayerContent(hash, namespaces, features, processedBy)
|
|
||||||
}
|
}
|
||||||
panic("required mock function not implemented")
|
panic("required mock function not implemented")
|
||||||
}
|
}
|
||||||
@ -136,13 +127,6 @@ func (ms *MockSession) FindLayer(name string) (Layer, bool, error) {
|
|||||||
panic("required mock function not implemented")
|
panic("required mock function not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *MockSession) FindLayerWithContent(name string) (LayerWithContent, bool, error) {
|
|
||||||
if ms.FctFindLayerWithContent != nil {
|
|
||||||
return ms.FctFindLayerWithContent(name)
|
|
||||||
}
|
|
||||||
panic("required mock function not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *MockSession) InsertVulnerabilities(vulnerabilities []VulnerabilityWithAffected) error {
|
func (ms *MockSession) InsertVulnerabilities(vulnerabilities []VulnerabilityWithAffected) error {
|
||||||
if ms.FctInsertVulnerabilities != nil {
|
if ms.FctInsertVulnerabilities != nil {
|
||||||
return ms.FctInsertVulnerabilities(vulnerabilities)
|
return ms.FctInsertVulnerabilities(vulnerabilities)
|
||||||
|
@ -41,25 +41,25 @@ type Ancestry struct {
|
|||||||
|
|
||||||
// AncestryLayer is a layer with all detected namespaced features.
|
// AncestryLayer is a layer with all detected namespaced features.
|
||||||
type AncestryLayer struct {
|
type AncestryLayer struct {
|
||||||
Layer
|
LayerMetadata
|
||||||
|
|
||||||
// DetectedFeatures are the features introduced by this layer when it was
|
// DetectedFeatures are the features introduced by this layer when it was
|
||||||
// processed.
|
// processed.
|
||||||
DetectedFeatures []NamespacedFeature
|
DetectedFeatures []NamespacedFeature
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layer contains the metadata of a layer.
|
// LayerMetadata contains the metadata of a layer.
|
||||||
type Layer struct {
|
type LayerMetadata struct {
|
||||||
// Hash is content hash of the layer.
|
// Hash is content hash of the layer.
|
||||||
Hash string
|
Hash string
|
||||||
// ProcessedBy contains the processors that processed this layer.
|
// ProcessedBy contains the processors that processed this layer.
|
||||||
ProcessedBy Processors
|
ProcessedBy Processors
|
||||||
}
|
}
|
||||||
|
|
||||||
// LayerWithContent is a layer with its detected namespaces and features by
|
// Layer is a layer with its detected namespaces and features by
|
||||||
// ProcessedBy.
|
// ProcessedBy.
|
||||||
type LayerWithContent struct {
|
type Layer struct {
|
||||||
Layer
|
LayerMetadata
|
||||||
|
|
||||||
Namespaces []Namespace
|
Namespaces []Namespace
|
||||||
Features []Feature
|
Features []Feature
|
||||||
|
@ -170,7 +170,7 @@ func (tx *pgSession) findAncestryLayers(id int64) ([]database.AncestryLayer, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !index.Valid || !id.Valid {
|
if !index.Valid || !id.Valid {
|
||||||
return nil, commonerr.ErrNotFound
|
panic("null ancestry ID or ancestry index violates database constraints")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := layers[index.Int64]; ok {
|
if _, ok := layers[index.Int64]; ok {
|
||||||
|
@ -30,7 +30,7 @@ func TestUpsertAncestry(t *testing.T) {
|
|||||||
Name: "a1",
|
Name: "a1",
|
||||||
Layers: []database.AncestryLayer{
|
Layers: []database.AncestryLayer{
|
||||||
{
|
{
|
||||||
Layer: database.Layer{
|
LayerMetadata: database.LayerMetadata{
|
||||||
Hash: "layer-N",
|
Hash: "layer-N",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -43,7 +43,7 @@ func TestUpsertAncestry(t *testing.T) {
|
|||||||
Name: "a",
|
Name: "a",
|
||||||
Layers: []database.AncestryLayer{
|
Layers: []database.AncestryLayer{
|
||||||
{
|
{
|
||||||
Layer: database.Layer{
|
LayerMetadata: database.LayerMetadata{
|
||||||
Hash: "layer-0",
|
Hash: "layer-0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -54,7 +54,7 @@ func TestUpsertAncestry(t *testing.T) {
|
|||||||
Name: "a",
|
Name: "a",
|
||||||
Layers: []database.AncestryLayer{
|
Layers: []database.AncestryLayer{
|
||||||
{
|
{
|
||||||
Layer: database.Layer{
|
LayerMetadata: database.LayerMetadata{
|
||||||
Hash: "layer-1",
|
Hash: "layer-1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -137,7 +137,7 @@ func assertAncestryEqual(t *testing.T, expected database.Ancestry, actual databa
|
|||||||
}
|
}
|
||||||
|
|
||||||
func assertAncestryLayerEqual(t *testing.T, expected database.AncestryLayer, actual database.AncestryLayer) bool {
|
func assertAncestryLayerEqual(t *testing.T, expected database.AncestryLayer, actual database.AncestryLayer) bool {
|
||||||
return assertLayerEqual(t, expected.Layer, actual.Layer) &&
|
return assertLayerEqual(t, expected.LayerMetadata, actual.LayerMetadata) &&
|
||||||
assertNamespacedFeatureEqual(t, expected.DetectedFeatures, actual.DetectedFeatures)
|
assertNamespacedFeatureEqual(t, expected.DetectedFeatures, actual.DetectedFeatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ func TestFindAncestry(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Layers: []database.AncestryLayer{
|
Layers: []database.AncestryLayer{
|
||||||
{
|
{
|
||||||
Layer: database.Layer{
|
LayerMetadata: database.LayerMetadata{
|
||||||
Hash: "layer-0",
|
Hash: "layer-0",
|
||||||
},
|
},
|
||||||
DetectedFeatures: []database.NamespacedFeature{
|
DetectedFeatures: []database.NamespacedFeature{
|
||||||
@ -188,17 +188,17 @@ func TestFindAncestry(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Layer: database.Layer{
|
LayerMetadata: database.LayerMetadata{
|
||||||
Hash: "layer-1",
|
Hash: "layer-1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Layer: database.Layer{
|
LayerMetadata: database.LayerMetadata{
|
||||||
Hash: "layer-2",
|
Hash: "layer-2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Layer: database.Layer{
|
LayerMetadata: database.LayerMetadata{
|
||||||
Hash: "layer-3b",
|
Hash: "layer-3b",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -23,19 +23,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (tx *pgSession) FindLayer(hash string) (database.Layer, bool, error) {
|
func (tx *pgSession) FindLayer(hash string) (database.Layer, bool, error) {
|
||||||
layer, _, ok, err := tx.findLayer(hash)
|
|
||||||
return layer, ok, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *pgSession) FindLayerWithContent(hash string) (database.LayerWithContent, bool, error) {
|
|
||||||
var (
|
var (
|
||||||
layer database.LayerWithContent
|
layer database.Layer
|
||||||
layerID int64
|
layerID int64
|
||||||
ok bool
|
ok bool
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
layer.Layer, layerID, ok, err = tx.findLayer(hash)
|
layer.LayerMetadata, layerID, ok, err = tx.findLayer(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return layer, false, err
|
return layer, false, err
|
||||||
}
|
}
|
||||||
@ -49,46 +44,53 @@ func (tx *pgSession) FindLayerWithContent(hash string) (database.LayerWithConten
|
|||||||
return layer, true, nil
|
return layer, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *pgSession) PersistLayer(hash string) error {
|
func (tx *pgSession) persistLayer(hash string) (int64, error) {
|
||||||
if hash == "" {
|
if hash == "" {
|
||||||
return commonerr.NewBadRequestError("Empty Layer Hash is not allowed")
|
return -1, commonerr.NewBadRequestError("Empty Layer Hash is not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := tx.Exec(queryPersistLayer(1), hash)
|
id := sql.NullInt64{}
|
||||||
if err != nil {
|
if err := tx.QueryRow(soiLayer, hash).Scan(&id); err != nil {
|
||||||
return handleError("queryPersistLayer", err)
|
return -1, handleError("queryPersistLayer", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if !id.Valid {
|
||||||
|
panic("null layer.id violates database constraint")
|
||||||
|
}
|
||||||
|
|
||||||
|
return id.Int64, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PersistLayerContent relates layer identified by hash with namespaces,
|
// PersistLayer relates layer identified by hash with namespaces,
|
||||||
// features and processors provided. If the layer, namespaces, features are not
|
// features and processors provided. If the layer, namespaces, features are not
|
||||||
// in database, the function returns an error.
|
// in database, the function returns an error.
|
||||||
func (tx *pgSession) PersistLayerContent(hash string, namespaces []database.Namespace, features []database.Feature, processedBy database.Processors) error {
|
func (tx *pgSession) PersistLayer(hash string, namespaces []database.Namespace, features []database.Feature, processedBy database.Processors) error {
|
||||||
if hash == "" {
|
if hash == "" {
|
||||||
return commonerr.NewBadRequestError("Empty layer hash is not allowed")
|
return commonerr.NewBadRequestError("Empty layer hash is not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var layerID int64
|
var (
|
||||||
err := tx.QueryRow(searchLayer, hash).Scan(&layerID)
|
err error
|
||||||
if err != nil {
|
id int64
|
||||||
|
)
|
||||||
|
|
||||||
|
if id, err = tx.persistLayer(hash); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tx.persistLayerNamespace(layerID, namespaces); err != nil {
|
if err = tx.persistLayerNamespace(id, namespaces); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tx.persistLayerFeatures(layerID, features); err != nil {
|
if err = tx.persistLayerFeatures(id, features); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tx.persistLayerDetectors(layerID, processedBy.Detectors); err != nil {
|
if err = tx.persistLayerDetectors(id, processedBy.Detectors); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tx.persistLayerListers(layerID, processedBy.Listers); err != nil {
|
if err = tx.persistLayerListers(id, processedBy.Listers); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,10 +277,10 @@ func (tx *pgSession) findLayerFeatures(layerID int64) ([]database.Feature, error
|
|||||||
return features, nil
|
return features, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *pgSession) findLayer(hash string) (database.Layer, int64, bool, error) {
|
func (tx *pgSession) findLayer(hash string) (database.LayerMetadata, int64, bool, error) {
|
||||||
var (
|
var (
|
||||||
layerID int64
|
layerID int64
|
||||||
layer = database.Layer{Hash: hash, ProcessedBy: database.Processors{}}
|
layer = database.LayerMetadata{Hash: hash, ProcessedBy: database.Processors{}}
|
||||||
)
|
)
|
||||||
|
|
||||||
if hash == "" {
|
if hash == "" {
|
||||||
|
@ -26,65 +26,72 @@ func TestPersistLayer(t *testing.T) {
|
|||||||
datastore, tx := openSessionForTest(t, "PersistLayer", false)
|
datastore, tx := openSessionForTest(t, "PersistLayer", false)
|
||||||
defer closeTest(t, datastore, tx)
|
defer closeTest(t, datastore, tx)
|
||||||
|
|
||||||
l1 := ""
|
|
||||||
l2 := "HESOYAM"
|
|
||||||
|
|
||||||
// invalid
|
// invalid
|
||||||
assert.NotNil(t, tx.PersistLayer(l1))
|
assert.NotNil(t, tx.PersistLayer("", nil, nil, database.Processors{}))
|
||||||
// valid
|
// insert namespaces + features to
|
||||||
assert.Nil(t, tx.PersistLayer(l2))
|
namespaces := []database.Namespace{
|
||||||
// duplicated
|
{
|
||||||
assert.Nil(t, tx.PersistLayer(l2))
|
Name: "sushi shop",
|
||||||
}
|
VersionFormat: "apk",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func TestPersistLayerProcessors(t *testing.T) {
|
features := []database.Feature{
|
||||||
datastore, tx := openSessionForTest(t, "PersistLayerProcessors", true)
|
{
|
||||||
defer closeTest(t, datastore, tx)
|
Name: "blue fin sashimi",
|
||||||
|
Version: "v1.0",
|
||||||
|
VersionFormat: "apk",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// invalid
|
processors := database.Processors{
|
||||||
assert.NotNil(t, tx.PersistLayerContent("hash", []database.Namespace{}, []database.Feature{}, database.Processors{}))
|
Listers: []string{"release"},
|
||||||
// valid
|
Detectors: []string{"apk"},
|
||||||
assert.Nil(t, tx.PersistLayerContent("layer-4", []database.Namespace{}, []database.Feature{}, database.Processors{Detectors: []string{"new detector!"}}))
|
}
|
||||||
|
|
||||||
|
assert.Nil(t, tx.PersistNamespaces(namespaces))
|
||||||
|
assert.Nil(t, tx.PersistFeatures(features))
|
||||||
|
|
||||||
|
// Valid
|
||||||
|
assert.Nil(t, tx.PersistLayer("RANDOM_FOREST", namespaces, features, processors))
|
||||||
|
|
||||||
|
nonExistingFeature := []database.Feature{{Name: "lobster sushi", Version: "v0.1", VersionFormat: "apk"}}
|
||||||
|
// Invalid:
|
||||||
|
assert.NotNil(t, tx.PersistLayer("RANDOM_FOREST", namespaces, nonExistingFeature, processors))
|
||||||
|
|
||||||
|
assert.Nil(t, tx.PersistFeatures(nonExistingFeature))
|
||||||
|
// Update the layer
|
||||||
|
assert.Nil(t, tx.PersistLayer("RANDOM_FOREST", namespaces, nonExistingFeature, processors))
|
||||||
|
|
||||||
|
// confirm update
|
||||||
|
layer, ok, err := tx.FindLayer("RANDOM_FOREST")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
expectedLayer := database.Layer{
|
||||||
|
LayerMetadata: database.LayerMetadata{
|
||||||
|
Hash: "RANDOM_FOREST",
|
||||||
|
ProcessedBy: processors,
|
||||||
|
},
|
||||||
|
Features: append(features, nonExistingFeature...),
|
||||||
|
Namespaces: namespaces,
|
||||||
|
}
|
||||||
|
|
||||||
|
assertLayerWithContentEqual(t, expectedLayer, layer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindLayer(t *testing.T) {
|
func TestFindLayer(t *testing.T) {
|
||||||
datastore, tx := openSessionForTest(t, "FindLayer", true)
|
datastore, tx := openSessionForTest(t, "FindLayer", true)
|
||||||
defer closeTest(t, datastore, tx)
|
defer closeTest(t, datastore, tx)
|
||||||
|
|
||||||
expected := database.Layer{
|
|
||||||
Hash: "layer-4",
|
|
||||||
ProcessedBy: database.Processors{
|
|
||||||
Detectors: []string{"os-release", "apt-sources"},
|
|
||||||
Listers: []string{"dpkg", "rpm"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// invalid
|
|
||||||
_, _, err := tx.FindLayer("")
|
_, _, err := tx.FindLayer("")
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
_, ok, err := tx.FindLayer("layer-non")
|
_, ok, err := tx.FindLayer("layer-non")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
|
||||||
// valid
|
expectedL := database.Layer{
|
||||||
layer, ok2, err := tx.FindLayer("layer-4")
|
LayerMetadata: database.LayerMetadata{
|
||||||
if assert.Nil(t, err) && assert.True(t, ok2) {
|
|
||||||
assertLayerEqual(t, expected, layer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindLayerWithContent(t *testing.T) {
|
|
||||||
datastore, tx := openSessionForTest(t, "FindLayerWithContent", true)
|
|
||||||
defer closeTest(t, datastore, tx)
|
|
||||||
|
|
||||||
_, _, err := tx.FindLayerWithContent("")
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
_, ok, err := tx.FindLayerWithContent("layer-non")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.False(t, ok)
|
|
||||||
|
|
||||||
expectedL := database.LayerWithContent{
|
|
||||||
Layer: database.Layer{
|
|
||||||
Hash: "layer-4",
|
Hash: "layer-4",
|
||||||
ProcessedBy: database.Processors{
|
ProcessedBy: database.Processors{
|
||||||
Detectors: []string{"os-release", "apt-sources"},
|
Detectors: []string{"os-release", "apt-sources"},
|
||||||
@ -101,19 +108,19 @@ func TestFindLayerWithContent(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
layer, ok2, err := tx.FindLayerWithContent("layer-4")
|
layer, ok2, err := tx.FindLayer("layer-4")
|
||||||
if assert.Nil(t, err) && assert.True(t, ok2) {
|
if assert.Nil(t, err) && assert.True(t, ok2) {
|
||||||
assertLayerWithContentEqual(t, expectedL, layer)
|
assertLayerWithContentEqual(t, expectedL, layer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertLayerWithContentEqual(t *testing.T, expected database.LayerWithContent, actual database.LayerWithContent) bool {
|
func assertLayerWithContentEqual(t *testing.T, expected database.Layer, actual database.Layer) bool {
|
||||||
return assertLayerEqual(t, expected.Layer, actual.Layer) &&
|
return assertLayerEqual(t, expected.LayerMetadata, actual.LayerMetadata) &&
|
||||||
assertFeaturesEqual(t, expected.Features, actual.Features) &&
|
assertFeaturesEqual(t, expected.Features, actual.Features) &&
|
||||||
assertNamespacesEqual(t, expected.Namespaces, actual.Namespaces)
|
assertNamespacesEqual(t, expected.Namespaces, actual.Namespaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertLayerEqual(t *testing.T, expected database.Layer, actual database.Layer) bool {
|
func assertLayerEqual(t *testing.T, expected database.LayerMetadata, actual database.LayerMetadata) bool {
|
||||||
return assertProcessorsEqual(t, expected.ProcessedBy, actual.ProcessedBy) &&
|
return assertProcessorsEqual(t, expected.ProcessedBy, actual.ProcessedBy) &&
|
||||||
assert.Equal(t, expected.Hash, actual.Hash)
|
assert.Equal(t, expected.Hash, actual.Hash)
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,16 @@ const (
|
|||||||
AND v.deleted_at IS NULL`
|
AND v.deleted_at IS NULL`
|
||||||
|
|
||||||
// layer.go
|
// layer.go
|
||||||
searchLayerIDs = `SELECT id, hash FROM layer WHERE hash = ANY($1);`
|
soiLayer = `
|
||||||
|
WITH new_layer AS (
|
||||||
|
INSERT INTO layer (hash)
|
||||||
|
SELECT CAST ($1 AS VARCHAR)
|
||||||
|
WHERE NOT EXISTS (SELECT id FROM layer WHERE hash = $1)
|
||||||
|
RETURNING id
|
||||||
|
)
|
||||||
|
SELECT id FROM new_Layer
|
||||||
|
UNION
|
||||||
|
SELECT id FROM layer WHERE hash = $1`
|
||||||
|
|
||||||
searchLayerFeatures = `
|
searchLayerFeatures = `
|
||||||
SELECT feature.Name, feature.Version, feature.version_format
|
SELECT feature.Name, feature.Version, feature.version_format
|
||||||
|
75
worker.go
75
worker.go
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2017 clair authors
|
// Copyright 2018 clair authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -146,32 +146,29 @@ func processRequests(imageFormat string, toDetect []processRequest) ([]database.
|
|||||||
return namespaces, features, updates, nil
|
return namespaces, features, updates, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLayer(datastore database.Datastore, req LayerRequest) (layer database.LayerWithContent, preq *processRequest, err error) {
|
func getLayer(datastore database.Datastore, req LayerRequest) (layer database.Layer, preq *processRequest, err error) {
|
||||||
var ok bool
|
var (
|
||||||
tx, err := datastore.Begin()
|
tx database.Session
|
||||||
if err != nil {
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if tx, err = datastore.Begin(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
layer, ok, err = tx.FindLayerWithContent(req.Hash)
|
if layer, ok, err = tx.FindLayer(req.Hash); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
err = tx.PersistLayer(req.Hash)
|
layer = database.Layer{
|
||||||
if err != nil {
|
LayerMetadata: database.LayerMetadata{
|
||||||
return
|
Hash: req.Hash,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tx.Commit(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
layer = database.LayerWithContent{}
|
|
||||||
layer.Hash = req.Hash
|
|
||||||
|
|
||||||
preq = &processRequest{
|
preq = &processRequest{
|
||||||
request: req,
|
request: req,
|
||||||
notProcessedBy: Processors,
|
notProcessedBy: Processors,
|
||||||
@ -185,15 +182,16 @@ func getLayer(datastore database.Datastore, req LayerRequest) (layer database.La
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// processLayers processes a set of post layer requests, stores layers and
|
// processLayers processes a set of post layer requests, stores layers and
|
||||||
// returns an ordered list of processed layers with detected features and
|
// returns an ordered list of processed layers with detected features and
|
||||||
// namespaces.
|
// namespaces.
|
||||||
func processLayers(datastore database.Datastore, imageFormat string, requests []LayerRequest) ([]database.LayerWithContent, error) {
|
func processLayers(datastore database.Datastore, imageFormat string, requests []LayerRequest) ([]database.Layer, error) {
|
||||||
toDetect := []processRequest{}
|
toDetect := []processRequest{}
|
||||||
layers := map[string]database.LayerWithContent{}
|
layers := map[string]database.Layer{}
|
||||||
for _, req := range requests {
|
for _, req := range requests {
|
||||||
if _, ok := layers[req.Hash]; ok {
|
if _, ok := layers[req.Hash]; ok {
|
||||||
continue
|
continue
|
||||||
@ -208,7 +206,7 @@ func processLayers(datastore database.Datastore, imageFormat string, requests []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespaces, features, partialRes, err := processRequests(imageFormat, toDetect)
|
namespaces, features, partialLayers, err := processRequests(imageFormat, toDetect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -222,10 +220,18 @@ func processLayers(datastore database.Datastore, imageFormat string, requests []
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, res := range partialRes {
|
for _, layer := range partialLayers {
|
||||||
if err := persistPartialLayer(datastore, res); err != nil {
|
if err := persistPartialLayer(datastore, layer); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"Hash": layer.hash,
|
||||||
|
"namespace count": len(layer.namespaces),
|
||||||
|
"feature count": len(layer.features),
|
||||||
|
"namespace detectors": layer.processedBy.Detectors,
|
||||||
|
"feature listers": layer.processedBy.Listers,
|
||||||
|
}).Debug("saved layer")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(Sida): The full layers are computed using partially
|
// NOTE(Sida): The full layers are computed using partially
|
||||||
@ -233,9 +239,9 @@ func processLayers(datastore database.Datastore, imageFormat string, requests []
|
|||||||
// Clair are changing some layers in this set of layers, it might generate
|
// Clair are changing some layers in this set of layers, it might generate
|
||||||
// different results especially when the other Clair is with different
|
// different results especially when the other Clair is with different
|
||||||
// processors.
|
// processors.
|
||||||
completeLayers := []database.LayerWithContent{}
|
completeLayers := []database.Layer{}
|
||||||
for _, req := range requests {
|
for _, req := range requests {
|
||||||
if partialLayer, ok := partialRes[req.Hash]; ok {
|
if partialLayer, ok := partialLayers[req.Hash]; ok {
|
||||||
completeLayers = append(completeLayers, combineLayers(layers[req.Hash], partialLayer))
|
completeLayers = append(completeLayers, combineLayers(layers[req.Hash], partialLayer))
|
||||||
} else {
|
} else {
|
||||||
completeLayers = append(completeLayers, layers[req.Hash])
|
completeLayers = append(completeLayers, layers[req.Hash])
|
||||||
@ -252,9 +258,10 @@ func persistPartialLayer(datastore database.Datastore, layer partialLayer) error
|
|||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
if err := tx.PersistLayerContent(layer.hash, layer.namespaces, layer.features, layer.processedBy); err != nil {
|
if err := tx.PersistLayer(layer.hash, layer.namespaces, layer.features, layer.processedBy); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,7 +293,7 @@ func persistNamespaces(datastore database.Datastore, namespaces []database.Names
|
|||||||
}
|
}
|
||||||
|
|
||||||
// combineLayers merges `layer` and `partial` without duplicated content.
|
// combineLayers merges `layer` and `partial` without duplicated content.
|
||||||
func combineLayers(layer database.LayerWithContent, partial partialLayer) database.LayerWithContent {
|
func combineLayers(layer database.Layer, partial partialLayer) database.Layer {
|
||||||
mapF := map[database.Feature]struct{}{}
|
mapF := map[database.Feature]struct{}{}
|
||||||
mapNS := map[database.Namespace]struct{}{}
|
mapNS := map[database.Namespace]struct{}{}
|
||||||
for _, f := range layer.Features {
|
for _, f := range layer.Features {
|
||||||
@ -312,8 +319,8 @@ func combineLayers(layer database.LayerWithContent, partial partialLayer) databa
|
|||||||
|
|
||||||
layer.ProcessedBy.Detectors = append(layer.ProcessedBy.Detectors, strutil.CompareStringLists(partial.processedBy.Detectors, layer.ProcessedBy.Detectors)...)
|
layer.ProcessedBy.Detectors = append(layer.ProcessedBy.Detectors, strutil.CompareStringLists(partial.processedBy.Detectors, layer.ProcessedBy.Detectors)...)
|
||||||
layer.ProcessedBy.Listers = append(layer.ProcessedBy.Listers, strutil.CompareStringLists(partial.processedBy.Listers, layer.ProcessedBy.Listers)...)
|
layer.ProcessedBy.Listers = append(layer.ProcessedBy.Listers, strutil.CompareStringLists(partial.processedBy.Listers, layer.ProcessedBy.Listers)...)
|
||||||
return database.LayerWithContent{
|
return database.Layer{
|
||||||
Layer: database.Layer{
|
LayerMetadata: database.LayerMetadata{
|
||||||
Hash: layer.Hash,
|
Hash: layer.Hash,
|
||||||
ProcessedBy: layer.ProcessedBy,
|
ProcessedBy: layer.ProcessedBy,
|
||||||
},
|
},
|
||||||
@ -346,7 +353,7 @@ func ProcessAncestry(datastore database.Datastore, imageFormat, name string, lay
|
|||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
ok bool
|
ok bool
|
||||||
layers []database.LayerWithContent
|
layers []database.Layer
|
||||||
commonProcessors database.Processors
|
commonProcessors database.Processors
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -361,7 +368,7 @@ func ProcessAncestry(datastore database.Datastore, imageFormat, name string, lay
|
|||||||
if ok, err = isAncestryProcessed(datastore, name); err != nil {
|
if ok, err = isAncestryProcessed(datastore, name); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
log.WithField("ancestry", name).Debug("Ancestry is processed")
|
log.WithField("name", name).Debug("ancestry is already processed")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,7 +393,7 @@ func getNamespacedFeatures(layers []database.AncestryLayer) []database.Namespace
|
|||||||
return features
|
return features
|
||||||
}
|
}
|
||||||
|
|
||||||
func processAncestry(datastore database.Datastore, name string, layers []database.LayerWithContent, commonProcessors database.Processors) error {
|
func processAncestry(datastore database.Datastore, name string, layers []database.Layer, commonProcessors database.Processors) error {
|
||||||
var (
|
var (
|
||||||
ancestry database.Ancestry
|
ancestry database.Ancestry
|
||||||
err error
|
err error
|
||||||
@ -458,7 +465,7 @@ func persistNamespacedFeatures(datastore database.Datastore, features []database
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getProcessors retrieves common subset of the processors of each layer.
|
// getProcessors retrieves common subset of the processors of each layer.
|
||||||
func getProcessors(layers []database.LayerWithContent) (database.Processors, error) {
|
func getProcessors(layers []database.Layer) (database.Processors, error) {
|
||||||
if len(layers) == 0 {
|
if len(layers) == 0 {
|
||||||
return database.Processors{}, nil
|
return database.Processors{}, nil
|
||||||
}
|
}
|
||||||
@ -495,7 +502,7 @@ type introducedFeature struct {
|
|||||||
|
|
||||||
// computeAncestryLayers computes ancestry's layers along with what features are
|
// computeAncestryLayers computes ancestry's layers along with what features are
|
||||||
// introduced.
|
// introduced.
|
||||||
func computeAncestryLayers(layers []database.LayerWithContent, commonProcessors database.Processors) ([]database.AncestryLayer, error) {
|
func computeAncestryLayers(layers []database.Layer, commonProcessors database.Processors) ([]database.AncestryLayer, error) {
|
||||||
// TODO(sidchen): Once the features are linked to specific processor, we
|
// TODO(sidchen): Once the features are linked to specific processor, we
|
||||||
// will use commonProcessors to filter out the features for this ancestry.
|
// will use commonProcessors to filter out the features for this ancestry.
|
||||||
|
|
||||||
@ -506,7 +513,7 @@ func computeAncestryLayers(layers []database.LayerWithContent, commonProcessors
|
|||||||
ancestryLayers := []database.AncestryLayer{}
|
ancestryLayers := []database.AncestryLayer{}
|
||||||
for index, layer := range layers {
|
for index, layer := range layers {
|
||||||
// Initialize the ancestry Layer
|
// Initialize the ancestry Layer
|
||||||
initializedLayer := database.AncestryLayer{Layer: layer.Layer, DetectedFeatures: []database.NamespacedFeature{}}
|
initializedLayer := database.AncestryLayer{LayerMetadata: layer.LayerMetadata, DetectedFeatures: []database.NamespacedFeature{}}
|
||||||
ancestryLayers = append(ancestryLayers, initializedLayer)
|
ancestryLayers = append(ancestryLayers, initializedLayer)
|
||||||
|
|
||||||
// Precondition: namespaces and features contain the result from union
|
// Precondition: namespaces and features contain the result from union
|
||||||
|
@ -40,7 +40,7 @@ import (
|
|||||||
type mockDatastore struct {
|
type mockDatastore struct {
|
||||||
database.MockDatastore
|
database.MockDatastore
|
||||||
|
|
||||||
layers map[string]database.LayerWithContent
|
layers map[string]database.Layer
|
||||||
ancestry map[string]database.Ancestry
|
ancestry map[string]database.Ancestry
|
||||||
namespaces map[string]database.Namespace
|
namespaces map[string]database.Namespace
|
||||||
features map[string]database.Feature
|
features map[string]database.Feature
|
||||||
@ -56,14 +56,14 @@ type mockSession struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func copyDatastore(md *mockDatastore) mockDatastore {
|
func copyDatastore(md *mockDatastore) mockDatastore {
|
||||||
layers := map[string]database.LayerWithContent{}
|
layers := map[string]database.Layer{}
|
||||||
for k, l := range md.layers {
|
for k, l := range md.layers {
|
||||||
features := append([]database.Feature(nil), l.Features...)
|
features := append([]database.Feature(nil), l.Features...)
|
||||||
namespaces := append([]database.Namespace(nil), l.Namespaces...)
|
namespaces := append([]database.Namespace(nil), l.Namespaces...)
|
||||||
listers := append([]string(nil), l.ProcessedBy.Listers...)
|
listers := append([]string(nil), l.ProcessedBy.Listers...)
|
||||||
detectors := append([]string(nil), l.ProcessedBy.Detectors...)
|
detectors := append([]string(nil), l.ProcessedBy.Detectors...)
|
||||||
layers[k] = database.LayerWithContent{
|
layers[k] = database.Layer{
|
||||||
Layer: database.Layer{
|
LayerMetadata: database.LayerMetadata{
|
||||||
Hash: l.Hash,
|
Hash: l.Hash,
|
||||||
ProcessedBy: database.Processors{
|
ProcessedBy: database.Processors{
|
||||||
Listers: listers,
|
Listers: listers,
|
||||||
@ -78,23 +78,23 @@ func copyDatastore(md *mockDatastore) mockDatastore {
|
|||||||
ancestry := map[string]database.Ancestry{}
|
ancestry := map[string]database.Ancestry{}
|
||||||
for k, a := range md.ancestry {
|
for k, a := range md.ancestry {
|
||||||
ancestryLayers := []database.AncestryLayer{}
|
ancestryLayers := []database.AncestryLayer{}
|
||||||
layers := []database.Layer{}
|
layers := []database.LayerMetadata{}
|
||||||
|
|
||||||
for _, layer := range a.Layers {
|
for _, layer := range a.Layers {
|
||||||
layers = append(layers, database.Layer{
|
layers = append(layers, database.LayerMetadata{
|
||||||
Hash: layer.Hash,
|
Hash: layer.Hash,
|
||||||
ProcessedBy: database.Processors{
|
ProcessedBy: database.Processors{
|
||||||
Detectors: append([]string(nil), layer.Layer.ProcessedBy.Detectors...),
|
Detectors: append([]string(nil), layer.LayerMetadata.ProcessedBy.Detectors...),
|
||||||
Listers: append([]string(nil), layer.Layer.ProcessedBy.Listers...),
|
Listers: append([]string(nil), layer.LayerMetadata.ProcessedBy.Listers...),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
ancestryLayers = append(ancestryLayers, database.AncestryLayer{
|
ancestryLayers = append(ancestryLayers, database.AncestryLayer{
|
||||||
Layer: database.Layer{
|
LayerMetadata: database.LayerMetadata{
|
||||||
Hash: layer.Hash,
|
Hash: layer.Hash,
|
||||||
ProcessedBy: database.Processors{
|
ProcessedBy: database.Processors{
|
||||||
Detectors: append([]string(nil), layer.Layer.ProcessedBy.Detectors...),
|
Detectors: append([]string(nil), layer.LayerMetadata.ProcessedBy.Detectors...),
|
||||||
Listers: append([]string(nil), layer.Layer.ProcessedBy.Listers...),
|
Listers: append([]string(nil), layer.LayerMetadata.ProcessedBy.Listers...),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DetectedFeatures: append([]database.NamespacedFeature(nil), layer.DetectedFeatures...),
|
DetectedFeatures: append([]database.NamespacedFeature(nil), layer.DetectedFeatures...),
|
||||||
@ -137,7 +137,7 @@ func copyDatastore(md *mockDatastore) mockDatastore {
|
|||||||
func newMockDatastore() *mockDatastore {
|
func newMockDatastore() *mockDatastore {
|
||||||
errSessionDone := errors.New("Session Done")
|
errSessionDone := errors.New("Session Done")
|
||||||
md := &mockDatastore{
|
md := &mockDatastore{
|
||||||
layers: make(map[string]database.LayerWithContent),
|
layers: make(map[string]database.Layer),
|
||||||
ancestry: make(map[string]database.Ancestry),
|
ancestry: make(map[string]database.Ancestry),
|
||||||
namespaces: make(map[string]database.Namespace),
|
namespaces: make(map[string]database.Namespace),
|
||||||
features: make(map[string]database.Feature),
|
features: make(map[string]database.Feature),
|
||||||
@ -186,27 +186,9 @@ func newMockDatastore() *mockDatastore {
|
|||||||
return database.Layer{}, false, errSessionDone
|
return database.Layer{}, false, errSessionDone
|
||||||
}
|
}
|
||||||
layer, ok := session.copy.layers[name]
|
layer, ok := session.copy.layers[name]
|
||||||
return layer.Layer, ok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
session.FctFindLayerWithContent = func(name string) (database.LayerWithContent, bool, error) {
|
|
||||||
if session.terminated {
|
|
||||||
return database.LayerWithContent{}, false, errSessionDone
|
|
||||||
}
|
|
||||||
layer, ok := session.copy.layers[name]
|
|
||||||
return layer, ok, nil
|
return layer, ok, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
session.FctPersistLayer = func(hash string) error {
|
|
||||||
if session.terminated {
|
|
||||||
return errSessionDone
|
|
||||||
}
|
|
||||||
if _, ok := session.copy.layers[hash]; !ok {
|
|
||||||
session.copy.layers[hash] = database.LayerWithContent{Layer: database.Layer{Hash: hash}}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
session.FctPersistNamespaces = func(ns []database.Namespace) error {
|
session.FctPersistNamespaces = func(ns []database.Namespace) error {
|
||||||
if session.terminated {
|
if session.terminated {
|
||||||
return errSessionDone
|
return errSessionDone
|
||||||
@ -234,15 +216,20 @@ func newMockDatastore() *mockDatastore {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
session.FctPersistLayerContent = func(hash string, namespaces []database.Namespace, features []database.Feature, processedBy database.Processors) error {
|
session.FctPersistLayer = func(hash string, namespaces []database.Namespace, features []database.Feature, processedBy database.Processors) error {
|
||||||
if session.terminated {
|
if session.terminated {
|
||||||
return errSessionDone
|
return errSessionDone
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the layer
|
// update the layer
|
||||||
|
_, ok := session.copy.layers[hash]
|
||||||
|
if !ok {
|
||||||
|
session.copy.layers[hash] = database.Layer{}
|
||||||
|
}
|
||||||
|
|
||||||
layer, ok := session.copy.layers[hash]
|
layer, ok := session.copy.layers[hash]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("layer not found")
|
return errors.New("Failed to insert layer")
|
||||||
}
|
}
|
||||||
|
|
||||||
layerFeatures := map[string]database.Feature{}
|
layerFeatures := map[string]database.Feature{}
|
||||||
@ -381,7 +368,7 @@ func TestProcessAncestryWithDistUpgrade(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, []database.Layer{
|
assert.Equal(t, []database.LayerMetadata{
|
||||||
{Hash: "blank"},
|
{Hash: "blank"},
|
||||||
{Hash: "wheezy"},
|
{Hash: "wheezy"},
|
||||||
{Hash: "jessie"},
|
{Hash: "jessie"},
|
||||||
@ -571,32 +558,32 @@ func TestComputeAncestryFeatures(t *testing.T) {
|
|||||||
// Suppose Clair is watching two files for namespaces one containing ns1
|
// Suppose Clair is watching two files for namespaces one containing ns1
|
||||||
// changes e.g. os-release and the other one containing ns2 changes e.g.
|
// changes e.g. os-release and the other one containing ns2 changes e.g.
|
||||||
// node.
|
// node.
|
||||||
blank := database.LayerWithContent{Layer: database.Layer{Hash: "blank"}}
|
blank := database.Layer{LayerMetadata: database.LayerMetadata{Hash: "blank"}}
|
||||||
initNS1a := database.LayerWithContent{
|
initNS1a := database.Layer{
|
||||||
Layer: database.Layer{Hash: "init ns1a"},
|
LayerMetadata: database.LayerMetadata{Hash: "init ns1a"},
|
||||||
Namespaces: []database.Namespace{ns1a},
|
Namespaces: []database.Namespace{ns1a},
|
||||||
Features: []database.Feature{f1, f2},
|
Features: []database.Feature{f1, f2},
|
||||||
}
|
}
|
||||||
|
|
||||||
upgradeNS2b := database.LayerWithContent{
|
upgradeNS2b := database.Layer{
|
||||||
Layer: database.Layer{Hash: "upgrade ns2b"},
|
LayerMetadata: database.LayerMetadata{Hash: "upgrade ns2b"},
|
||||||
Namespaces: []database.Namespace{ns2b},
|
Namespaces: []database.Namespace{ns2b},
|
||||||
}
|
}
|
||||||
|
|
||||||
upgradeNS1b := database.LayerWithContent{
|
upgradeNS1b := database.Layer{
|
||||||
Layer: database.Layer{Hash: "upgrade ns1b"},
|
LayerMetadata: database.LayerMetadata{Hash: "upgrade ns1b"},
|
||||||
Namespaces: []database.Namespace{ns1b},
|
Namespaces: []database.Namespace{ns1b},
|
||||||
Features: []database.Feature{f1, f2},
|
Features: []database.Feature{f1, f2},
|
||||||
}
|
}
|
||||||
|
|
||||||
initNS2a := database.LayerWithContent{
|
initNS2a := database.Layer{
|
||||||
Layer: database.Layer{Hash: "init ns2a"},
|
LayerMetadata: database.LayerMetadata{Hash: "init ns2a"},
|
||||||
Namespaces: []database.Namespace{ns2a},
|
Namespaces: []database.Namespace{ns2a},
|
||||||
Features: []database.Feature{f3, f4},
|
Features: []database.Feature{f3, f4},
|
||||||
}
|
}
|
||||||
|
|
||||||
removeF2 := database.LayerWithContent{
|
removeF2 := database.Layer{
|
||||||
Layer: database.Layer{Hash: "remove f2"},
|
LayerMetadata: database.LayerMetadata{Hash: "remove f2"},
|
||||||
Features: []database.Feature{f1},
|
Features: []database.Feature{f1},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,7 +596,7 @@ func TestComputeAncestryFeatures(t *testing.T) {
|
|||||||
// -> f1 (remove f2)
|
// -> f1 (remove f2)
|
||||||
// -> blank (empty)
|
// -> blank (empty)
|
||||||
|
|
||||||
layers := []database.LayerWithContent{
|
layers := []database.Layer{
|
||||||
blank,
|
blank,
|
||||||
initNS1a,
|
initNS1a,
|
||||||
removeF2,
|
removeF2,
|
||||||
|
Loading…
Reference in New Issue
Block a user