Initial services refactoring of database.Datastore

This breaks out the following service interfaces:
 - locks.Service
 - keyvalue.Service
 - notifications.Service
 - vulnerabilities.Service

This also updates the Mock implementations along similar lines.

Make Travis work on my fork by rsyncing the build dir as coreos/clair
This commit is contained in:
Matt Moore 2016-05-31 06:25:56 -07:00
parent 951efed1ff
commit c823c39da5
61 changed files with 1151 additions and 824 deletions

View File

@ -14,6 +14,9 @@ install:
- echo 'nop' - echo 'nop'
script: script:
- mkdir -p $HOME/gopath/src/github.com/coreos/clair/
- rsync -az ${TRAVIS_BUILD_DIR}/ $HOME/gopath/src/github.com/coreos/clair/
- cd $HOME/gopath/src/github.com/coreos/clair/
- go test -v $(go list ./... | grep -v /vendor/) - go test -v $(go list ./... | grep -v /vendor/)
services: services:

36
Dockerfile.test Normal file
View File

@ -0,0 +1,36 @@
# Copyright 2015 clair authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
FROM golang:1.6
MAINTAINER Quentin Machu <quentin.machu@coreos.com>
RUN apt-get update && \
apt-get install -y bzr rpm xz-utils && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # 18MAR2016
VOLUME /config
EXPOSE 6060 6061
ADD . /go/src/github.com/coreos/clair/
WORKDIR /go/src/github.com/coreos/clair/
RUN go install -v github.com/coreos/clair/cmd/clair
RUN go test $(go list ./... | grep -v /vendor/)
ENTRYPOINT ["clair"]

View File

@ -24,7 +24,10 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/coreos/clair/config" "github.com/coreos/clair/config"
"github.com/coreos/clair/database" "github.com/coreos/clair/services/keyvalue"
"github.com/coreos/clair/services/locks"
"github.com/coreos/clair/services/notifications"
"github.com/coreos/clair/services/vulnerabilities"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
) )
@ -59,6 +62,9 @@ func HTTPHandler(handler Handler, ctx *RouteContext) httprouter.Handle {
} }
type RouteContext struct { type RouteContext struct {
Store database.Datastore LockService locks.Service
Config *config.APIConfig KeyValueStore keyvalue.Service
VulnerabilityStore vulnerabilities.Service
NotificationState notifications.Service
Config *config.APIConfig
} }

View File

@ -66,9 +66,18 @@ func getHealth(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx
header := w.Header() header := w.Header()
header.Set("Server", "clair") header.Set("Server", "clair")
status := http.StatusInternalServerError status := http.StatusOK
if ctx.Store.Ping() { if !ctx.LockService.Ping() {
status = http.StatusOK status = http.StatusInternalServerError
}
if !ctx.KeyValueStore.Ping() {
status = http.StatusInternalServerError
}
if !ctx.VulnerabilityStore.Ping() {
status = http.StatusInternalServerError
}
if !ctx.NotificationState.Ping() {
status = http.StatusInternalServerError
} }
w.WriteHeader(status) w.WriteHeader(status)

View File

@ -21,7 +21,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
"github.com/fernet/fernet-go" "github.com/fernet/fernet-go"
@ -44,7 +44,7 @@ type Layer struct {
Features []Feature `json:"Features,omitempty"` Features []Feature `json:"Features,omitempty"`
} }
func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabilities bool) Layer { func LayerFromDatabaseModel(dbLayer services.Layer, withFeatures, withVulnerabilities bool) Layer {
layer := Layer{ layer := Layer{
Name: dbLayer.Name, Name: dbLayer.Name,
IndexedByVersion: dbLayer.EngineVersion, IndexedByVersion: dbLayer.EngineVersion,
@ -104,25 +104,25 @@ type Vulnerability struct {
FixedIn []Feature `json:"FixedIn,omitempty"` FixedIn []Feature `json:"FixedIn,omitempty"`
} }
func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) { func (v Vulnerability) DatabaseModel() (services.Vulnerability, error) {
severity := types.Priority(v.Severity) severity := types.Priority(v.Severity)
if !severity.IsValid() { if !severity.IsValid() {
return database.Vulnerability{}, errors.New("Invalid severity") return services.Vulnerability{}, errors.New("Invalid severity")
} }
var dbFeatures []database.FeatureVersion var dbFeatures []services.FeatureVersion
for _, feature := range v.FixedIn { for _, feature := range v.FixedIn {
dbFeature, err := feature.DatabaseModel() dbFeature, err := feature.DatabaseModel()
if err != nil { if err != nil {
return database.Vulnerability{}, err return services.Vulnerability{}, err
} }
dbFeatures = append(dbFeatures, dbFeature) dbFeatures = append(dbFeatures, dbFeature)
} }
return database.Vulnerability{ return services.Vulnerability{
Name: v.Name, Name: v.Name,
Namespace: database.Namespace{Name: v.NamespaceName}, Namespace: services.Namespace{Name: v.NamespaceName},
Description: v.Description, Description: v.Description,
Link: v.Link, Link: v.Link,
Severity: severity, Severity: severity,
@ -131,7 +131,7 @@ func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) {
}, nil }, nil
} }
func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability, withFixedIn bool) Vulnerability { func VulnerabilityFromDatabaseModel(dbVuln services.Vulnerability, withFixedIn bool) Vulnerability {
vuln := Vulnerability{ vuln := Vulnerability{
Name: dbVuln.Name, Name: dbVuln.Name,
NamespaceName: dbVuln.Namespace.Name, NamespaceName: dbVuln.Namespace.Name,
@ -158,7 +158,7 @@ type Feature struct {
AddedBy string `json:"AddedBy,omitempty"` AddedBy string `json:"AddedBy,omitempty"`
} }
func FeatureFromDatabaseModel(dbFeatureVersion database.FeatureVersion) Feature { func FeatureFromDatabaseModel(dbFeatureVersion services.FeatureVersion) Feature {
versionStr := dbFeatureVersion.Version.String() versionStr := dbFeatureVersion.Version.String()
if versionStr == types.MaxVersion.String() { if versionStr == types.MaxVersion.String() {
versionStr = "None" versionStr = "None"
@ -172,7 +172,7 @@ func FeatureFromDatabaseModel(dbFeatureVersion database.FeatureVersion) Feature
} }
} }
func (f Feature) DatabaseModel() (database.FeatureVersion, error) { func (f Feature) DatabaseModel() (services.FeatureVersion, error) {
var version types.Version var version types.Version
if f.Version == "None" { if f.Version == "None" {
version = types.MaxVersion version = types.MaxVersion
@ -180,14 +180,14 @@ func (f Feature) DatabaseModel() (database.FeatureVersion, error) {
var err error var err error
version, err = types.NewVersion(f.Version) version, err = types.NewVersion(f.Version)
if err != nil { if err != nil {
return database.FeatureVersion{}, err return services.FeatureVersion{}, err
} }
} }
return database.FeatureVersion{ return services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Name: f.Name, Name: f.Name,
Namespace: database.Namespace{Name: f.NamespaceName}, Namespace: services.Namespace{Name: f.NamespaceName},
}, },
Version: version, Version: version,
}, nil }, nil
@ -205,7 +205,7 @@ type Notification struct {
New *VulnerabilityWithLayers `json:"New,omitempty"` New *VulnerabilityWithLayers `json:"New,omitempty"`
} }
func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotification, limit int, pageToken string, nextPage database.VulnerabilityNotificationPageNumber, key string) Notification { func NotificationFromDatabaseModel(dbNotification services.VulnerabilityNotification, limit int, pageToken string, nextPage services.VulnerabilityNotificationPageNumber, key string) Notification {
var oldVuln *VulnerabilityWithLayers var oldVuln *VulnerabilityWithLayers
if dbNotification.OldVulnerability != nil { if dbNotification.OldVulnerability != nil {
v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.OldVulnerability) v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.OldVulnerability)
@ -219,7 +219,7 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica
} }
var nextPageStr string var nextPageStr string
if nextPage != database.NoVulnerabilityNotificationPage { if nextPage != services.NoVulnerabilityNotificationPage {
nextPageBytes, _ := tokenMarshal(nextPage, key) nextPageBytes, _ := tokenMarshal(nextPage, key)
nextPageStr = string(nextPageBytes) nextPageStr = string(nextPageBytes)
} }
@ -255,7 +255,7 @@ type VulnerabilityWithLayers struct {
LayersIntroducingVulnerability []string `json:"LayersIntroducingVulnerability,omitempty"` LayersIntroducingVulnerability []string `json:"LayersIntroducingVulnerability,omitempty"`
} }
func VulnerabilityWithLayersFromDatabaseModel(dbVuln database.Vulnerability) VulnerabilityWithLayers { func VulnerabilityWithLayersFromDatabaseModel(dbVuln services.Vulnerability) VulnerabilityWithLayers {
vuln := VulnerabilityFromDatabaseModel(dbVuln, true) vuln := VulnerabilityFromDatabaseModel(dbVuln, true)
var layers []string var layers []string

View File

@ -26,7 +26,7 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/coreos/clair/api/context" "github.com/coreos/clair/api/context"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/worker" "github.com/coreos/clair/worker"
@ -109,7 +109,7 @@ func postLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx
return postLayerRoute, http.StatusBadRequest return postLayerRoute, http.StatusBadRequest
} }
err = worker.Process(ctx.Store, request.Layer.Format, request.Layer.Name, request.Layer.ParentName, request.Layer.Path, request.Layer.Headers) err = worker.Process(ctx.VulnerabilityStore, request.Layer.Format, request.Layer.Name, request.Layer.ParentName, request.Layer.Path, request.Layer.Headers)
if err != nil { if err != nil {
if err == utils.ErrCouldNotExtract || if err == utils.ErrCouldNotExtract ||
err == utils.ErrExtractedFileTooBig || err == utils.ErrExtractedFileTooBig ||
@ -142,7 +142,7 @@ func getLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *
_, withFeatures := r.URL.Query()["features"] _, withFeatures := r.URL.Query()["features"]
_, withVulnerabilities := r.URL.Query()["vulnerabilities"] _, withVulnerabilities := r.URL.Query()["vulnerabilities"]
dbLayer, err := ctx.Store.FindLayer(p.ByName("layerName"), withFeatures, withVulnerabilities) dbLayer, err := ctx.VulnerabilityStore.FindLayer(p.ByName("layerName"), withFeatures, withVulnerabilities)
if err == cerrors.ErrNotFound { if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, LayerEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, LayerEnvelope{Error: &Error{err.Error()}})
return getLayerRoute, http.StatusNotFound return getLayerRoute, http.StatusNotFound
@ -158,7 +158,7 @@ func getLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *
} }
func deleteLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func deleteLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
err := ctx.Store.DeleteLayer(p.ByName("layerName")) err := ctx.VulnerabilityStore.DeleteLayer(p.ByName("layerName"))
if err == cerrors.ErrNotFound { if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, LayerEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, LayerEnvelope{Error: &Error{err.Error()}})
return deleteLayerRoute, http.StatusNotFound return deleteLayerRoute, http.StatusNotFound
@ -172,7 +172,7 @@ func deleteLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ct
} }
func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
dbNamespaces, err := ctx.Store.ListNamespaces() dbNamespaces, err := ctx.VulnerabilityStore.ListNamespaces()
if err != nil { if err != nil {
writeResponse(w, r, http.StatusInternalServerError, NamespaceEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusInternalServerError, NamespaceEnvelope{Error: &Error{err.Error()}})
return getNamespacesRoute, http.StatusInternalServerError return getNamespacesRoute, http.StatusInternalServerError
@ -219,7 +219,7 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par
return getNotificationRoute, http.StatusBadRequest return getNotificationRoute, http.StatusBadRequest
} }
dbVulns, nextPage, err := ctx.Store.ListVulnerabilities(namespace, limit, page) dbVulns, nextPage, err := ctx.VulnerabilityStore.ListVulnerabilities(namespace, limit, page)
if err == cerrors.ErrNotFound { if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return getVulnerabilityRoute, http.StatusNotFound return getVulnerabilityRoute, http.StatusNotFound
@ -267,7 +267,7 @@ func postVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Para
return postVulnerabilityRoute, http.StatusBadRequest return postVulnerabilityRoute, http.StatusBadRequest
} }
err = ctx.Store.InsertVulnerabilities([]database.Vulnerability{vuln}, true) err = ctx.VulnerabilityStore.InsertVulnerabilities([]services.Vulnerability{vuln}, true)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case *cerrors.ErrBadRequest: case *cerrors.ErrBadRequest:
@ -286,7 +286,7 @@ func postVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Para
func getVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func getVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
_, withFixedIn := r.URL.Query()["fixedIn"] _, withFixedIn := r.URL.Query()["fixedIn"]
dbVuln, err := ctx.Store.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName")) dbVuln, err := ctx.VulnerabilityStore.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
if err == cerrors.ErrNotFound { if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return getVulnerabilityRoute, http.StatusNotFound return getVulnerabilityRoute, http.StatusNotFound
@ -328,7 +328,7 @@ func putVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Param
vuln.Namespace.Name = p.ByName("namespaceName") vuln.Namespace.Name = p.ByName("namespaceName")
vuln.Name = p.ByName("vulnerabilityName") vuln.Name = p.ByName("vulnerabilityName")
err = ctx.Store.InsertVulnerabilities([]database.Vulnerability{vuln}, true) err = ctx.VulnerabilityStore.InsertVulnerabilities([]services.Vulnerability{vuln}, true)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case *cerrors.ErrBadRequest: case *cerrors.ErrBadRequest:
@ -345,7 +345,7 @@ func putVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Param
} }
func deleteVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func deleteVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
err := ctx.Store.DeleteVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName")) err := ctx.VulnerabilityStore.DeleteVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
if err == cerrors.ErrNotFound { if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return deleteVulnerabilityRoute, http.StatusNotFound return deleteVulnerabilityRoute, http.StatusNotFound
@ -359,7 +359,7 @@ func deleteVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Pa
} }
func getFixes(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func getFixes(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
dbVuln, err := ctx.Store.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName")) dbVuln, err := ctx.VulnerabilityStore.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
if err == cerrors.ErrNotFound { if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}})
return getFixesRoute, http.StatusNotFound return getFixesRoute, http.StatusNotFound
@ -397,7 +397,7 @@ func putFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *co
return putFixRoute, http.StatusBadRequest return putFixRoute, http.StatusBadRequest
} }
err = ctx.Store.InsertVulnerabilityFixes(p.ByName("vulnerabilityNamespace"), p.ByName("vulnerabilityName"), []database.FeatureVersion{dbFix}) err = ctx.VulnerabilityStore.InsertVulnerabilityFixes(p.ByName("vulnerabilityNamespace"), p.ByName("vulnerabilityName"), []services.FeatureVersion{dbFix})
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case *cerrors.ErrBadRequest: case *cerrors.ErrBadRequest:
@ -418,7 +418,7 @@ func putFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *co
} }
func deleteFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func deleteFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
err := ctx.Store.DeleteVulnerabilityFix(p.ByName("vulnerabilityNamespace"), p.ByName("vulnerabilityName"), p.ByName("fixName")) err := ctx.VulnerabilityStore.DeleteVulnerabilityFix(p.ByName("vulnerabilityNamespace"), p.ByName("vulnerabilityName"), p.ByName("fixName"))
if err == cerrors.ErrNotFound { if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}})
return deleteFixRoute, http.StatusNotFound return deleteFixRoute, http.StatusNotFound
@ -446,7 +446,7 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params
} }
var pageToken string var pageToken string
page := database.VulnerabilityNotificationFirstPage page := services.VulnerabilityNotificationFirstPage
pageStrs, pageExists := query["page"] pageStrs, pageExists := query["page"]
if pageExists { if pageExists {
err := tokenUnmarshal(pageStrs[0], ctx.Config.PaginationKey, &page) err := tokenUnmarshal(pageStrs[0], ctx.Config.PaginationKey, &page)
@ -464,7 +464,7 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params
pageToken = string(pageTokenBytes) pageToken = string(pageTokenBytes)
} }
dbNotification, nextPage, err := ctx.Store.GetNotification(p.ByName("notificationName"), limit, page) dbNotification, nextPage, err := ctx.NotificationState.GetNotification(p.ByName("notificationName"), limit, page)
if err == cerrors.ErrNotFound { if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, NotificationEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, NotificationEnvelope{Error: &Error{err.Error()}})
return deleteNotificationRoute, http.StatusNotFound return deleteNotificationRoute, http.StatusNotFound
@ -480,7 +480,7 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params
} }
func deleteNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func deleteNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
err := ctx.Store.DeleteNotification(p.ByName("notificationName")) err := ctx.NotificationState.DeleteNotification(p.ByName("notificationName"))
if err == cerrors.ErrNotFound { if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, NotificationEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, NotificationEnvelope{Error: &Error{err.Error()}})
return deleteNotificationRoute, http.StatusNotFound return deleteNotificationRoute, http.StatusNotFound

View File

@ -26,8 +26,11 @@ import (
"github.com/coreos/clair/api" "github.com/coreos/clair/api"
"github.com/coreos/clair/api/context" "github.com/coreos/clair/api/context"
"github.com/coreos/clair/config" "github.com/coreos/clair/config"
"github.com/coreos/clair/database"
"github.com/coreos/clair/notifier" "github.com/coreos/clair/notifier"
"github.com/coreos/clair/services/keyvalue"
"github.com/coreos/clair/services/locks"
"github.com/coreos/clair/services/notifications"
"github.com/coreos/clair/services/vulnerabilities"
"github.com/coreos/clair/updater" "github.com/coreos/clair/updater"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
@ -41,26 +44,45 @@ func Boot(config *config.Config) {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
st := utils.NewStopper() st := utils.NewStopper()
// Open database // Open services
db, err := database.Open(config.Database) ls, err := locks.Open(config.Database)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer db.Close() defer ls.Close()
kvs, err := keyvalue.Open(config.Database)
if err != nil {
log.Fatal(err)
}
defer kvs.Close()
vuln, err := vulnerabilities.Open(config.Database)
if err != nil {
log.Fatal(err)
}
defer vuln.Close()
ns, err := notifications.Open(config.Database)
if err != nil {
log.Fatal(err)
}
defer ns.Close()
// Start notifier // Start notifier
st.Begin() st.Begin()
go notifier.Run(config.Notifier, db, st) go notifier.Run(config.Notifier, ls, ns, st)
// Start API // Start API
st.Begin() st.Begin()
go api.Run(config.API, &context.RouteContext{db, config.API}, st) ctx := &context.RouteContext{ls, kvs, vuln, ns, config.API}
go api.Run(config.API, ctx, st)
st.Begin() st.Begin()
go api.RunHealth(config.API, &context.RouteContext{db, config.API}, st) go api.RunHealth(config.API, ctx, st)
// Start updater // Start updater
st.Begin() st.Begin()
go updater.Run(config.Updater, db, st) go updater.Run(config.Updater, ls, kvs, vuln, st)
// Wait for interruption and shutdown gracefully. // Wait for interruption and shutdown gracefully.
waitForSignals(syscall.SIGINT, syscall.SIGTERM) waitForSignals(syscall.SIGINT, syscall.SIGTERM)

View File

@ -14,171 +14,3 @@
// Package database defines the Clair's models and a common interface for database implementations. // Package database defines the Clair's models and a common interface for database implementations.
package database package database
import (
"errors"
"fmt"
"time"
"github.com/coreos/clair/config"
)
var (
// ErrBackendException is an error that occurs when the database backend does
// not work properly (ie. unreachable).
ErrBackendException = errors.New("database: an error occured when querying the backend")
// ErrInconsistent is an error that occurs when a database consistency check
// fails (ie. when an entity which is supposed to be unique is detected twice)
ErrInconsistent = errors.New("database: inconsistent database")
)
var drivers = make(map[string]Driver)
// Driver is a function that opens a Datastore specified by its database driver type and specific
// configuration.
type Driver func(config.RegistrableComponentConfig) (Datastore, error)
// Register makes a Constructor available by the provided name.
//
// If this function is called twice with the same name or if the Constructor is
// nil, it panics.
func Register(name string, driver Driver) {
if driver == nil {
panic("database: could not register nil Driver")
}
if _, dup := drivers[name]; dup {
panic("database: could not register duplicate Driver: " + name)
}
drivers[name] = driver
}
// Open opens a Datastore specified by a configuration.
func Open(cfg config.RegistrableComponentConfig) (Datastore, error) {
driver, ok := drivers[cfg.Type]
if !ok {
return nil, fmt.Errorf("database: unknown Driver %q (forgotten configuration or import?)", cfg.Type)
}
return driver(cfg)
}
// Datastore is the interface that describes a database backend implementation.
type Datastore interface {
// # Namespace
// ListNamespaces returns the entire list of known Namespaces.
ListNamespaces() ([]Namespace, error)
// # Layer
// InsertLayer stores a Layer in the database.
// A Layer is uniquely identified by its Name. The Name and EngineVersion fields are mandatory.
// If a Parent is specified, it is expected that it has been retrieved using FindLayer.
// If a Layer that already exists is inserted and the EngineVersion of the given Layer is higher
// than the stored one, the stored Layer should be updated.
// The function has to be idempotent, inserting a layer that already exists shouln'd return an
// error.
InsertLayer(Layer) error
// FindLayer retrieves a Layer from the database.
// withFeatures specifies whether the Features field should be filled. When withVulnerabilities is
// true, the Features field should be filled and their AffectedBy fields should contain every
// vulnerabilities that affect them.
FindLayer(name string, withFeatures, withVulnerabilities bool) (Layer, error)
// DeleteLayer deletes a Layer from the database and every layers that are based on it,
// recursively.
DeleteLayer(name string) error
// # Vulnerability
// ListVulnerabilities returns the list of vulnerabilies of a certain Namespace.
// The Limit and page parameters are used to paginate the return list.
// The first given page should be 0. The function will then return the next available page.
// If there is no more page, -1 has to be returned.
ListVulnerabilities(namespaceName string, limit int, page int) ([]Vulnerability, int, error)
// InsertVulnerabilities stores the given Vulnerabilities in the database, updating them if
// necessary. A vulnerability is uniquely identified by its Namespace and its Name.
// The FixedIn field may only contain a partial list of Features that are affected by the
// Vulnerability, along with the version in which the vulnerability is fixed. It is the
// responsibility of the implementation to update the list properly. A version equals to
// types.MinVersion means that the given Feature is not being affected by the Vulnerability at
// all and thus, should be removed from the list. It is important that Features should be unique
// in the FixedIn list. For example, it doesn't make sense to have two `openssl` Feature listed as
// a Vulnerability can only be fixed in one Version. This is true because Vulnerabilities and
// Features are Namespaced (i.e. specific to one operating system).
// Each vulnerability insertion or update has to create a Notification that will contain the
// old and the updated Vulnerability, unless createNotification equals to true.
InsertVulnerabilities(vulnerabilities []Vulnerability, createNotification bool) error
// FindVulnerability retrieves a Vulnerability from the database, including the FixedIn list.
FindVulnerability(namespaceName, name string) (Vulnerability, error)
// DeleteVulnerability removes a Vulnerability from the database.
// It has to create a Notification that will contain the old Vulnerability.
DeleteVulnerability(namespaceName, name string) error
// InsertVulnerabilityFixes adds new FixedIn Feature or update the Versions of existing ones to
// the specified Vulnerability in the database.
// It has has to create a Notification that will contain the old and the updated Vulnerability.
InsertVulnerabilityFixes(vulnerabilityNamespace, vulnerabilityName string, fixes []FeatureVersion) error
// DeleteVulnerabilityFix removes a FixedIn Feature from the specified Vulnerability in the
// database. It can be used to store the fact that a Vulnerability no longer affects the given
// Feature in any Version.
// It has has to create a Notification that will contain the old and the updated Vulnerability.
DeleteVulnerabilityFix(vulnerabilityNamespace, vulnerabilityName, featureName string) error
// # Notification
// GetAvailableNotification returns the Name, Created, Notified and Deleted fields of a
// Notification that should be handled. The renotify interval defines how much time after being
// marked as Notified by SetNotificationNotified, a Notification that hasn't been deleted should
// be returned again by this function. A Notification for which there is a valid Lock with the
// same Name should not be returned.
GetAvailableNotification(renotifyInterval time.Duration) (VulnerabilityNotification, error)
// GetNotification returns a Notification, including its OldVulnerability and NewVulnerability
// fields. On these Vulnerabilities, LayersIntroducingVulnerability should be filled with
// every Layer that introduces the Vulnerability (i.e. adds at least one affected FeatureVersion).
// The Limit and page parameters are used to paginate LayersIntroducingVulnerability. The first
// given page should be VulnerabilityNotificationFirstPage. The function will then return the next
// availage page. If there is no more page, NoVulnerabilityNotificationPage has to be returned.
GetNotification(name string, limit int, page VulnerabilityNotificationPageNumber) (VulnerabilityNotification, VulnerabilityNotificationPageNumber, error)
// SetNotificationNotified marks a Notification as notified and thus, makes it unavailable for
// GetAvailableNotification, until the renotify duration is elapsed.
SetNotificationNotified(name string) error
// DeleteNotification marks a Notification as deleted, and thus, makes it unavailable for
// GetAvailableNotification.
DeleteNotification(name string) error
// # Key/Value
// InsertKeyValue stores or updates a simple key/value pair in the database.
InsertKeyValue(key, value string) error
// GetKeyValue retrieves a value from the database from the given key.
// It returns an empty string if there is no such key.
GetKeyValue(key string) (string, error)
// # Lock
// Lock creates or renew a Lock in the database with the given name, owner and duration.
// After the specified duration, the Lock expires by itself if it hasn't been unlocked, and thus,
// let other users create a Lock with the same name. However, the owner can renew its Lock by
// setting renew to true. Lock should not block, it should instead returns whether the Lock has
// been successfully acquired/renewed. If it's the case, the expiration time of that Lock is
// returned as well.
Lock(name string, owner string, duration time.Duration, renew bool) (bool, time.Time)
// Unlock releases an existing Lock.
Unlock(name, owner string)
// FindLock returns the owner of a Lock specified by the name, and its experation time if it
// exists.
FindLock(name string) (string, time.Time, error)
// # Miscellaneous
// Ping returns the health status of the database.
Ping() bool
// Close closes the database and free any allocated resource.
Close()
}

View File

@ -14,153 +14,49 @@
package database package database
import "time" import (
"github.com/coreos/clair/services"
"time"
)
// MockDatastore implements Datastore and enables overriding each available method. // MockBase implements services.Base
type MockBase struct {
FctPing func() bool
FctClose func()
}
func (mds *MockBase) Ping() bool {
if mds.FctPing != nil {
return mds.FctPing()
}
panic("required mock function not implemented")
}
func (mds *MockBase) Close() {
if mds.FctClose != nil {
mds.FctClose()
return
}
panic("required mock function not implemented")
}
// MockLock implements locks.Service and enables overriding each available method.
// The default behavior of each method is to simply panic. // The default behavior of each method is to simply panic.
type MockDatastore struct { type MockLock struct {
FctListNamespaces func() ([]Namespace, error) MockBase
FctInsertLayer func(Layer) error FctLock func(name string, owner string, duration time.Duration, renew bool) (bool, time.Time)
FctFindLayer func(name string, withFeatures, withVulnerabilities bool) (Layer, error) FctUnlock func(name, owner string)
FctDeleteLayer func(name string) error FctFindLock func(name string) (string, time.Time, error)
FctListVulnerabilities func(namespaceName string, limit int, page int) ([]Vulnerability, int, error)
FctInsertVulnerabilities func(vulnerabilities []Vulnerability, createNotification bool) error
FctFindVulnerability func(namespaceName, name string) (Vulnerability, error)
FctDeleteVulnerability func(namespaceName, name string) error
FctInsertVulnerabilityFixes func(vulnerabilityNamespace, vulnerabilityName string, fixes []FeatureVersion) error
FctDeleteVulnerabilityFix func(vulnerabilityNamespace, vulnerabilityName, featureName string) error
FctGetAvailableNotification func(renotifyInterval time.Duration) (VulnerabilityNotification, error)
FctGetNotification func(name string, limit int, page VulnerabilityNotificationPageNumber) (VulnerabilityNotification, VulnerabilityNotificationPageNumber, error)
FctSetNotificationNotified func(name string) error
FctDeleteNotification func(name string) error
FctInsertKeyValue func(key, value string) error
FctGetKeyValue func(key string) (string, error)
FctLock func(name string, owner string, duration time.Duration, renew bool) (bool, time.Time)
FctUnlock func(name, owner string)
FctFindLock func(name string) (string, time.Time, error)
FctPing func() bool
FctClose func()
} }
func (mds *MockDatastore) ListNamespaces() ([]Namespace, error) { func (mds *MockLock) Lock(name string, owner string, duration time.Duration, renew bool) (bool, time.Time) {
if mds.FctListNamespaces != nil {
return mds.FctListNamespaces()
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) InsertLayer(layer Layer) error {
if mds.FctInsertLayer != nil {
return mds.FctInsertLayer(layer)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) FindLayer(name string, withFeatures, withVulnerabilities bool) (Layer, error) {
if mds.FctFindLayer != nil {
return mds.FctFindLayer(name, withFeatures, withVulnerabilities)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) DeleteLayer(name string) error {
if mds.FctDeleteLayer != nil {
return mds.FctDeleteLayer(name)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) ListVulnerabilities(namespaceName string, limit int, page int) ([]Vulnerability, int, error) {
if mds.FctListVulnerabilities != nil {
return mds.FctListVulnerabilities(namespaceName, limit, page)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) InsertVulnerabilities(vulnerabilities []Vulnerability, createNotification bool) error {
if mds.FctInsertVulnerabilities != nil {
return mds.FctInsertVulnerabilities(vulnerabilities, createNotification)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) FindVulnerability(namespaceName, name string) (Vulnerability, error) {
if mds.FctFindVulnerability != nil {
return mds.FctFindVulnerability(namespaceName, name)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) DeleteVulnerability(namespaceName, name string) error {
if mds.FctDeleteVulnerability != nil {
return mds.FctDeleteVulnerability(namespaceName, name)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) InsertVulnerabilityFixes(vulnerabilityNamespace, vulnerabilityName string, fixes []FeatureVersion) error {
if mds.FctInsertVulnerabilityFixes != nil {
return mds.FctInsertVulnerabilityFixes(vulnerabilityNamespace, vulnerabilityName, fixes)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) DeleteVulnerabilityFix(vulnerabilityNamespace, vulnerabilityName, featureName string) error {
if mds.FctDeleteVulnerabilityFix != nil {
return mds.FctDeleteVulnerabilityFix(vulnerabilityNamespace, vulnerabilityName, featureName)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) GetAvailableNotification(renotifyInterval time.Duration) (VulnerabilityNotification, error) {
if mds.FctGetAvailableNotification != nil {
return mds.FctGetAvailableNotification(renotifyInterval)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) GetNotification(name string, limit int, page VulnerabilityNotificationPageNumber) (VulnerabilityNotification, VulnerabilityNotificationPageNumber, error) {
if mds.FctGetNotification != nil {
return mds.FctGetNotification(name, limit, page)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) SetNotificationNotified(name string) error {
if mds.FctSetNotificationNotified != nil {
return mds.FctSetNotificationNotified(name)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) DeleteNotification(name string) error {
if mds.FctDeleteNotification != nil {
return mds.FctDeleteNotification(name)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) InsertKeyValue(key, value string) error {
if mds.FctInsertKeyValue != nil {
return mds.FctInsertKeyValue(key, value)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) GetKeyValue(key string) (string, error) {
if mds.FctGetKeyValue != nil {
return mds.FctGetKeyValue(key)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) Lock(name string, owner string, duration time.Duration, renew bool) (bool, time.Time) {
if mds.FctLock != nil { if mds.FctLock != nil {
return mds.FctLock(name, owner, duration, renew) return mds.FctLock(name, owner, duration, renew)
} }
panic("required mock function not implemented") panic("required mock function not implemented")
} }
func (mds *MockDatastore) Unlock(name, owner string) { func (mds *MockLock) Unlock(name, owner string) {
if mds.FctUnlock != nil { if mds.FctUnlock != nil {
mds.FctUnlock(name, owner) mds.FctUnlock(name, owner)
return return
@ -168,24 +64,155 @@ func (mds *MockDatastore) Unlock(name, owner string) {
panic("required mock function not implemented") panic("required mock function not implemented")
} }
func (mds *MockDatastore) FindLock(name string) (string, time.Time, error) { func (mds *MockLock) FindLock(name string) (string, time.Time, error) {
if mds.FctFindLock != nil { if mds.FctFindLock != nil {
return mds.FctFindLock(name) return mds.FctFindLock(name)
} }
panic("required mock function not implemented") panic("required mock function not implemented")
} }
func (mds *MockDatastore) Ping() bool { // MockKeyValue implements keyvalue.Service and enables overriding each available method.
if mds.FctPing != nil { // The default behavior of each method is to simply panic.
return mds.FctPing() type MockKeyValue struct {
MockBase
FctInsertKeyValue func(key, value string) error
FctGetKeyValue func(key string) (string, error)
}
func (mds *MockKeyValue) InsertKeyValue(key, value string) error {
if mds.FctInsertKeyValue != nil {
return mds.FctInsertKeyValue(key, value)
} }
panic("required mock function not implemented") panic("required mock function not implemented")
} }
func (mds *MockDatastore) Close() { func (mds *MockKeyValue) GetKeyValue(key string) (string, error) {
if mds.FctClose != nil { if mds.FctGetKeyValue != nil {
mds.FctClose() return mds.FctGetKeyValue(key)
return }
panic("required mock function not implemented")
}
// MockNotification implements notifications.Service and enables overriding each available method.
// The default behavior of each method is to simply panic.
type MockNotification struct {
MockBase
FctGetAvailableNotification func(renotifyInterval time.Duration) (services.VulnerabilityNotification, error)
FctGetNotification func(name string, limit int, page services.VulnerabilityNotificationPageNumber) (services.VulnerabilityNotification, services.VulnerabilityNotificationPageNumber, error)
FctSetNotificationNotified func(name string) error
FctDeleteNotification func(name string) error
}
func (mds *MockNotification) GetAvailableNotification(renotifyInterval time.Duration) (services.VulnerabilityNotification, error) {
if mds.FctGetAvailableNotification != nil {
return mds.FctGetAvailableNotification(renotifyInterval)
}
panic("required mock function not implemented")
}
func (mds *MockNotification) GetNotification(name string, limit int, page services.VulnerabilityNotificationPageNumber) (services.VulnerabilityNotification, services.VulnerabilityNotificationPageNumber, error) {
if mds.FctGetNotification != nil {
return mds.FctGetNotification(name, limit, page)
}
panic("required mock function not implemented")
}
func (mds *MockNotification) SetNotificationNotified(name string) error {
if mds.FctSetNotificationNotified != nil {
return mds.FctSetNotificationNotified(name)
}
panic("required mock function not implemented")
}
func (mds *MockNotification) DeleteNotification(name string) error {
if mds.FctDeleteNotification != nil {
return mds.FctDeleteNotification(name)
}
panic("required mock function not implemented")
}
// MockVulnerabilities implements vulnerabilities.Service and enables overriding each available method.
// The default behavior of each method is to simply panic.
type MockVulnerabilities struct {
MockBase
FctListNamespaces func() ([]services.Namespace, error)
FctInsertLayer func(services.Layer) error
FctFindLayer func(name string, withFeatures, withVulnerabilities bool) (services.Layer, error)
FctDeleteLayer func(name string) error
FctListVulnerabilities func(namespaceName string, limit int, page int) ([]services.Vulnerability, int, error)
FctInsertVulnerabilities func(vulnerabilities []services.Vulnerability, createNotification bool) error
FctFindVulnerability func(namespaceName, name string) (services.Vulnerability, error)
FctDeleteVulnerability func(namespaceName, name string) error
FctInsertVulnerabilityFixes func(vulnerabilityNamespace, vulnerabilityName string, fixes []services.FeatureVersion) error
FctDeleteVulnerabilityFix func(vulnerabilityNamespace, vulnerabilityName, featureName string) error
}
func (mds *MockVulnerabilities) ListNamespaces() ([]services.Namespace, error) {
if mds.FctListNamespaces != nil {
return mds.FctListNamespaces()
}
panic("required mock function not implemented")
}
func (mds *MockVulnerabilities) InsertLayer(layer services.Layer) error {
if mds.FctInsertLayer != nil {
return mds.FctInsertLayer(layer)
}
panic("required mock function not implemented")
}
func (mds *MockVulnerabilities) FindLayer(name string, withFeatures, withVulnerabilities bool) (services.Layer, error) {
if mds.FctFindLayer != nil {
return mds.FctFindLayer(name, withFeatures, withVulnerabilities)
}
panic("required mock function not implemented")
}
func (mds *MockVulnerabilities) DeleteLayer(name string) error {
if mds.FctDeleteLayer != nil {
return mds.FctDeleteLayer(name)
}
panic("required mock function not implemented")
}
func (mds *MockVulnerabilities) ListVulnerabilities(namespaceName string, limit int, page int) ([]services.Vulnerability, int, error) {
if mds.FctListVulnerabilities != nil {
return mds.FctListVulnerabilities(namespaceName, limit, page)
}
panic("required mock function not implemented")
}
func (mds *MockVulnerabilities) InsertVulnerabilities(vulnerabilities []services.Vulnerability, createNotification bool) error {
if mds.FctInsertVulnerabilities != nil {
return mds.FctInsertVulnerabilities(vulnerabilities, createNotification)
}
panic("required mock function not implemented")
}
func (mds *MockVulnerabilities) FindVulnerability(namespaceName, name string) (services.Vulnerability, error) {
if mds.FctFindVulnerability != nil {
return mds.FctFindVulnerability(namespaceName, name)
}
panic("required mock function not implemented")
}
func (mds *MockVulnerabilities) DeleteVulnerability(namespaceName, name string) error {
if mds.FctDeleteVulnerability != nil {
return mds.FctDeleteVulnerability(namespaceName, name)
}
panic("required mock function not implemented")
}
func (mds *MockVulnerabilities) InsertVulnerabilityFixes(vulnerabilityNamespace, vulnerabilityName string, fixes []services.FeatureVersion) error {
if mds.FctInsertVulnerabilityFixes != nil {
return mds.FctInsertVulnerabilityFixes(vulnerabilityNamespace, vulnerabilityName, fixes)
}
panic("required mock function not implemented")
}
func (mds *MockVulnerabilities) DeleteVulnerabilityFix(vulnerabilityNamespace, vulnerabilityName, featureName string) error {
if mds.FctDeleteVulnerabilityFix != nil {
return mds.FctDeleteVulnerabilityFix(vulnerabilityNamespace, vulnerabilityName, featureName)
} }
panic("required mock function not implemented") panic("required mock function not implemented")
} }

View File

@ -26,7 +26,7 @@ import (
"github.com/pborman/uuid" "github.com/pborman/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
) )
@ -45,8 +45,8 @@ func TestRaceAffects(t *testing.T) {
defer datastore.Close() defer datastore.Close()
// Insert the Feature on which we'll work. // Insert the Feature on which we'll work.
feature := database.Feature{ feature := services.Feature{
Namespace: database.Namespace{Name: "TestRaceAffectsFeatureNamespace1"}, Namespace: services.Namespace{Name: "TestRaceAffectsFeatureNamespace1"},
Name: "TestRaceAffecturesFeature1", Name: "TestRaceAffecturesFeature1",
} }
_, err = datastore.insertFeature(feature) _, err = datastore.insertFeature(feature)
@ -60,11 +60,11 @@ func TestRaceAffects(t *testing.T) {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
// Generate FeatureVersions. // Generate FeatureVersions.
featureVersions := make([]database.FeatureVersion, numFeatureVersions) featureVersions := make([]services.FeatureVersion, numFeatureVersions)
for i := 0; i < numFeatureVersions; i++ { for i := 0; i < numFeatureVersions; i++ {
version := rand.Intn(numFeatureVersions) version := rand.Intn(numFeatureVersions)
featureVersions[i] = database.FeatureVersion{ featureVersions[i] = services.FeatureVersion{
Feature: feature, Feature: feature,
Version: types.NewVersionUnsafe(strconv.Itoa(version)), Version: types.NewVersionUnsafe(strconv.Itoa(version)),
} }
@ -72,18 +72,18 @@ func TestRaceAffects(t *testing.T) {
// Generate vulnerabilities. // Generate vulnerabilities.
// They are mapped by fixed version, which will make verification really easy afterwards. // They are mapped by fixed version, which will make verification really easy afterwards.
vulnerabilities := make(map[int][]database.Vulnerability) vulnerabilities := make(map[int][]services.Vulnerability)
for i := 0; i < numVulnerabilities; i++ { for i := 0; i < numVulnerabilities; i++ {
version := rand.Intn(numFeatureVersions) + 1 version := rand.Intn(numFeatureVersions) + 1
// if _, ok := vulnerabilities[version]; !ok { // if _, ok := vulnerabilities[version]; !ok {
// vulnerabilities[version] = make([]database.Vulnerability) // vulnerabilities[version] = make([]services.Vulnerability)
// } // }
vulnerability := database.Vulnerability{ vulnerability := services.Vulnerability{
Name: uuid.New(), Name: uuid.New(),
Namespace: feature.Namespace, Namespace: feature.Namespace,
FixedIn: []database.FeatureVersion{ FixedIn: []services.FeatureVersion{
{ {
Feature: feature, Feature: feature,
Version: types.NewVersionUnsafe(strconv.Itoa(version)), Version: types.NewVersionUnsafe(strconv.Itoa(version)),
@ -103,7 +103,7 @@ func TestRaceAffects(t *testing.T) {
defer wg.Done() defer wg.Done()
for _, vulnerabilitiesM := range vulnerabilities { for _, vulnerabilitiesM := range vulnerabilities {
for _, vulnerability := range vulnerabilitiesM { for _, vulnerability := range vulnerabilitiesM {
err = datastore.InsertVulnerabilities([]database.Vulnerability{vulnerability}, true) err = datastore.InsertVulnerabilities([]services.Vulnerability{vulnerability}, true)
assert.Nil(t, err) assert.Nil(t, err)
} }
} }

View File

@ -18,12 +18,12 @@ import (
"database/sql" "database/sql"
"time" "time"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
) )
func (pgSQL *pgSQL) insertFeature(feature database.Feature) (int, error) { func (pgSQL *pgSQL) insertFeature(feature services.Feature) (int, error) {
if feature.Name == "" { if feature.Name == "" {
return 0, cerrors.NewBadRequestError("could not find/insert invalid Feature") return 0, cerrors.NewBadRequestError("could not find/insert invalid Feature")
} }
@ -61,7 +61,7 @@ func (pgSQL *pgSQL) insertFeature(feature database.Feature) (int, error) {
return id, nil return id, nil
} }
func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion) (id int, err error) { func (pgSQL *pgSQL) insertFeatureVersion(featureVersion services.FeatureVersion) (id int, err error) {
if featureVersion.Version.String() == "" { if featureVersion.Version.String() == "" {
return 0, cerrors.NewBadRequestError("could not find/insert invalid FeatureVersion") return 0, cerrors.NewBadRequestError("could not find/insert invalid FeatureVersion")
} }
@ -177,7 +177,7 @@ func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion)
} }
// TODO(Quentin-M): Batch me // TODO(Quentin-M): Batch me
func (pgSQL *pgSQL) insertFeatureVersions(featureVersions []database.FeatureVersion) ([]int, error) { func (pgSQL *pgSQL) insertFeatureVersions(featureVersions []services.FeatureVersion) ([]int, error) {
IDs := make([]int, 0, len(featureVersions)) IDs := make([]int, 0, len(featureVersions))
for i := 0; i < len(featureVersions); i++ { for i := 0; i < len(featureVersions); i++ {
@ -197,7 +197,7 @@ type vulnerabilityAffectsFeatureVersion struct {
fixedInVersion types.Version fixedInVersion types.Version
} }
func linkFeatureVersionToVulnerabilities(tx *sql.Tx, featureVersion database.FeatureVersion) error { func linkFeatureVersionToVulnerabilities(tx *sql.Tx, featureVersion services.FeatureVersion) error {
// Select every vulnerability and the fixed version that affect this Feature. // Select every vulnerability and the fixed version that affect this Feature.
// TODO(Quentin-M): LIMIT // TODO(Quentin-M): LIMIT
rows, err := tx.Query(searchVulnerabilityFixedInFeature, featureVersion.Feature.ID) rows, err := tx.Query(searchVulnerabilityFixedInFeature, featureVersion.Feature.ID)

View File

@ -19,7 +19,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
) )
@ -32,20 +32,20 @@ func TestInsertFeature(t *testing.T) {
defer datastore.Close() defer datastore.Close()
// Invalid Feature. // Invalid Feature.
id0, err := datastore.insertFeature(database.Feature{}) id0, err := datastore.insertFeature(services.Feature{})
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Zero(t, id0) assert.Zero(t, id0)
id0, err = datastore.insertFeature(database.Feature{ id0, err = datastore.insertFeature(services.Feature{
Namespace: database.Namespace{}, Namespace: services.Namespace{},
Name: "TestInsertFeature0", Name: "TestInsertFeature0",
}) })
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Zero(t, id0) assert.Zero(t, id0)
// Insert Feature and ensure we can find it. // Insert Feature and ensure we can find it.
feature := database.Feature{ feature := services.Feature{
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace1"}, Namespace: services.Namespace{Name: "TestInsertFeatureNamespace1"},
Name: "TestInsertFeature1", Name: "TestInsertFeature1",
} }
id1, err := datastore.insertFeature(feature) id1, err := datastore.insertFeature(feature)
@ -55,28 +55,28 @@ func TestInsertFeature(t *testing.T) {
assert.Equal(t, id1, id2) assert.Equal(t, id1, id2)
// Insert invalid FeatureVersion. // Insert invalid FeatureVersion.
for _, invalidFeatureVersion := range []database.FeatureVersion{ for _, invalidFeatureVersion := range []services.FeatureVersion{
{ {
Feature: database.Feature{}, Feature: services.Feature{},
Version: types.NewVersionUnsafe("1.0"), Version: types.NewVersionUnsafe("1.0"),
}, },
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{}, Namespace: services.Namespace{},
Name: "TestInsertFeature2", Name: "TestInsertFeature2",
}, },
Version: types.NewVersionUnsafe("1.0"), Version: types.NewVersionUnsafe("1.0"),
}, },
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace2"}, Namespace: services.Namespace{Name: "TestInsertFeatureNamespace2"},
Name: "TestInsertFeature2", Name: "TestInsertFeature2",
}, },
Version: types.NewVersionUnsafe(""), Version: types.NewVersionUnsafe(""),
}, },
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace2"}, Namespace: services.Namespace{Name: "TestInsertFeatureNamespace2"},
Name: "TestInsertFeature2", Name: "TestInsertFeature2",
}, },
Version: types.NewVersionUnsafe("bad version"), Version: types.NewVersionUnsafe("bad version"),
@ -88,9 +88,9 @@ func TestInsertFeature(t *testing.T) {
} }
// Insert FeatureVersion and ensure we can find it. // Insert FeatureVersion and ensure we can find it.
featureVersion := database.FeatureVersion{ featureVersion := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace1"}, Namespace: services.Namespace{Name: "TestInsertFeatureNamespace1"},
Name: "TestInsertFeature1", Name: "TestInsertFeature1",
}, },
Version: types.NewVersionUnsafe("2:3.0-imba"), Version: types.NewVersionUnsafe("2:3.0-imba"),

View File

@ -18,13 +18,13 @@ import (
"database/sql" "database/sql"
"time" "time"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/guregu/null/zero" "github.com/guregu/null/zero"
) )
func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities bool) (database.Layer, error) { func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities bool) (services.Layer, error) {
subquery := "all" subquery := "all"
if withFeatures { if withFeatures {
subquery += "/features" subquery += "/features"
@ -34,7 +34,7 @@ func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities boo
defer observeQueryTime("FindLayer", subquery, time.Now()) defer observeQueryTime("FindLayer", subquery, time.Now())
// Find the layer // Find the layer
var layer database.Layer var layer services.Layer
var parentID zero.Int var parentID zero.Int
var parentName zero.String var parentName zero.String
var namespaceID zero.Int var namespaceID zero.Int
@ -49,14 +49,14 @@ func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities boo
} }
if !parentID.IsZero() { if !parentID.IsZero() {
layer.Parent = &database.Layer{ layer.Parent = &services.Layer{
Model: database.Model{ID: int(parentID.Int64)}, Model: services.Model{ID: int(parentID.Int64)},
Name: parentName.String, Name: parentName.String,
} }
} }
if !namespaceID.IsZero() { if !namespaceID.IsZero() {
layer.Namespace = &database.Namespace{ layer.Namespace = &services.Namespace{
Model: database.Model{ID: int(namespaceID.Int64)}, Model: services.Model{ID: int(namespaceID.Int64)},
Name: namespaceName.String, Name: namespaceName.String,
} }
} }
@ -110,9 +110,9 @@ func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities boo
return layer, nil return layer, nil
} }
// getLayerFeatureVersions returns list of database.FeatureVersion that a database.Layer has. // getLayerFeatureVersions returns list of services.FeatureVersion that a services.Layer has.
func getLayerFeatureVersions(tx *sql.Tx, layerID int) ([]database.FeatureVersion, error) { func getLayerFeatureVersions(tx *sql.Tx, layerID int) ([]services.FeatureVersion, error) {
var featureVersions []database.FeatureVersion var featureVersions []services.FeatureVersion
// Query. // Query.
rows, err := tx.Query(searchLayerFeatureVersion, layerID) rows, err := tx.Query(searchLayerFeatureVersion, layerID)
@ -123,9 +123,9 @@ func getLayerFeatureVersions(tx *sql.Tx, layerID int) ([]database.FeatureVersion
// Scan query. // Scan query.
var modification string var modification string
mapFeatureVersions := make(map[int]database.FeatureVersion) mapFeatureVersions := make(map[int]services.FeatureVersion)
for rows.Next() { for rows.Next() {
var featureVersion database.FeatureVersion var featureVersion services.FeatureVersion
err = rows.Scan(&featureVersion.ID, &modification, &featureVersion.Feature.Namespace.ID, err = rows.Scan(&featureVersion.ID, &modification, &featureVersion.Feature.Namespace.ID,
&featureVersion.Feature.Namespace.Name, &featureVersion.Feature.ID, &featureVersion.Feature.Namespace.Name, &featureVersion.Feature.ID,
@ -143,7 +143,7 @@ func getLayerFeatureVersions(tx *sql.Tx, layerID int) ([]database.FeatureVersion
delete(mapFeatureVersions, featureVersion.ID) delete(mapFeatureVersions, featureVersion.ID)
default: default:
log.Warningf("unknown Layer_diff_FeatureVersion's modification: %s", modification) log.Warningf("unknown Layer_diff_FeatureVersion's modification: %s", modification)
return featureVersions, database.ErrInconsistent return featureVersions, services.ErrInconsistent
} }
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
@ -158,9 +158,9 @@ func getLayerFeatureVersions(tx *sql.Tx, layerID int) ([]database.FeatureVersion
return featureVersions, nil return featureVersions, nil
} }
// loadAffectedBy returns the list of database.Vulnerability that affect the given // loadAffectedBy returns the list of services.Vulnerability that affect the given
// FeatureVersion. // FeatureVersion.
func loadAffectedBy(tx *sql.Tx, featureVersions []database.FeatureVersion) error { func loadAffectedBy(tx *sql.Tx, featureVersions []services.FeatureVersion) error {
if len(featureVersions) == 0 { if len(featureVersions) == 0 {
return nil return nil
} }
@ -178,10 +178,10 @@ func loadAffectedBy(tx *sql.Tx, featureVersions []database.FeatureVersion) error
} }
defer rows.Close() defer rows.Close()
vulnerabilities := make(map[int][]database.Vulnerability, len(featureVersions)) vulnerabilities := make(map[int][]services.Vulnerability, len(featureVersions))
var featureversionID int var featureversionID int
for rows.Next() { for rows.Next() {
var vulnerability database.Vulnerability var vulnerability services.Vulnerability
err := rows.Scan(&featureversionID, &vulnerability.ID, &vulnerability.Name, err := rows.Scan(&featureversionID, &vulnerability.ID, &vulnerability.Name,
&vulnerability.Description, &vulnerability.Link, &vulnerability.Severity, &vulnerability.Description, &vulnerability.Link, &vulnerability.Severity,
&vulnerability.Metadata, &vulnerability.Namespace.Name, &vulnerability.FixedBy) &vulnerability.Metadata, &vulnerability.Namespace.Name, &vulnerability.FixedBy)
@ -209,7 +209,7 @@ func loadAffectedBy(tx *sql.Tx, featureVersions []database.FeatureVersion) error
// (happens when Feature detectors relies on the detected layer Namespace). However, if the listed // (happens when Feature detectors relies on the detected layer Namespace). However, if the listed
// Feature has the same Name/Version as its parent, InsertLayer considers that the Feature hasn't // Feature has the same Name/Version as its parent, InsertLayer considers that the Feature hasn't
// been modified. // been modified.
func (pgSQL *pgSQL) InsertLayer(layer database.Layer) error { func (pgSQL *pgSQL) InsertLayer(layer services.Layer) error {
tf := time.Now() tf := time.Now()
// Verify parameters // Verify parameters
@ -314,10 +314,10 @@ func (pgSQL *pgSQL) InsertLayer(layer database.Layer) error {
return nil return nil
} }
func (pgSQL *pgSQL) updateDiffFeatureVersions(tx *sql.Tx, layer, existingLayer *database.Layer) error { func (pgSQL *pgSQL) updateDiffFeatureVersions(tx *sql.Tx, layer, existingLayer *services.Layer) error {
// add and del are the FeatureVersion diff we should insert. // add and del are the FeatureVersion diff we should insert.
var add []database.FeatureVersion var add []services.FeatureVersion
var del []database.FeatureVersion var del []services.FeatureVersion
if layer.Parent == nil { if layer.Parent == nil {
// There is no parent, every Features are added. // There is no parent, every Features are added.
@ -369,8 +369,8 @@ func (pgSQL *pgSQL) updateDiffFeatureVersions(tx *sql.Tx, layer, existingLayer *
return nil return nil
} }
func createNV(features []database.FeatureVersion) (map[string]*database.FeatureVersion, []string) { func createNV(features []services.FeatureVersion) (map[string]*services.FeatureVersion, []string) {
mapNV := make(map[string]*database.FeatureVersion, 0) mapNV := make(map[string]*services.FeatureVersion, 0)
sliceNV := make([]string, 0, len(features)) sliceNV := make([]string, 0, len(features))
for i := 0; i < len(features); i++ { for i := 0; i < len(features); i++ {

View File

@ -20,7 +20,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/services/vulnerabilities"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
) )
@ -123,93 +124,93 @@ func TestInsertLayer(t *testing.T) {
testInsertLayerDelete(t, datastore) testInsertLayerDelete(t, datastore)
} }
func testInsertLayerInvalid(t *testing.T, datastore database.Datastore) { func testInsertLayerInvalid(t *testing.T, ls vulnerabilities.Service) {
invalidLayers := []database.Layer{ invalidLayers := []services.Layer{
{}, {},
{Name: "layer0", Parent: &database.Layer{}}, {Name: "layer0", Parent: &services.Layer{}},
{Name: "layer0", Parent: &database.Layer{Name: "UnknownLayer"}}, {Name: "layer0", Parent: &services.Layer{Name: "UnknownLayer"}},
} }
for _, invalidLayer := range invalidLayers { for _, invalidLayer := range invalidLayers {
err := datastore.InsertLayer(invalidLayer) err := ls.InsertLayer(invalidLayer)
assert.Error(t, err) assert.Error(t, err)
} }
} }
func testInsertLayerTree(t *testing.T, datastore database.Datastore) { func testInsertLayerTree(t *testing.T, ls vulnerabilities.Service) {
f1 := database.FeatureVersion{ f1 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "TestInsertLayerNamespace2"}, Namespace: services.Namespace{Name: "TestInsertLayerNamespace2"},
Name: "TestInsertLayerFeature1", Name: "TestInsertLayerFeature1",
}, },
Version: types.NewVersionUnsafe("1.0"), Version: types.NewVersionUnsafe("1.0"),
} }
f2 := database.FeatureVersion{ f2 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "TestInsertLayerNamespace2"}, Namespace: services.Namespace{Name: "TestInsertLayerNamespace2"},
Name: "TestInsertLayerFeature2", Name: "TestInsertLayerFeature2",
}, },
Version: types.NewVersionUnsafe("0.34"), Version: types.NewVersionUnsafe("0.34"),
} }
f3 := database.FeatureVersion{ f3 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "TestInsertLayerNamespace2"}, Namespace: services.Namespace{Name: "TestInsertLayerNamespace2"},
Name: "TestInsertLayerFeature3", Name: "TestInsertLayerFeature3",
}, },
Version: types.NewVersionUnsafe("0.56"), Version: types.NewVersionUnsafe("0.56"),
} }
f4 := database.FeatureVersion{ f4 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"}, Namespace: services.Namespace{Name: "TestInsertLayerNamespace3"},
Name: "TestInsertLayerFeature2", Name: "TestInsertLayerFeature2",
}, },
Version: types.NewVersionUnsafe("0.34"), Version: types.NewVersionUnsafe("0.34"),
} }
f5 := database.FeatureVersion{ f5 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"}, Namespace: services.Namespace{Name: "TestInsertLayerNamespace3"},
Name: "TestInsertLayerFeature3", Name: "TestInsertLayerFeature3",
}, },
Version: types.NewVersionUnsafe("0.56"), Version: types.NewVersionUnsafe("0.56"),
} }
f6 := database.FeatureVersion{ f6 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"}, Namespace: services.Namespace{Name: "TestInsertLayerNamespace3"},
Name: "TestInsertLayerFeature4", Name: "TestInsertLayerFeature4",
}, },
Version: types.NewVersionUnsafe("0.666"), Version: types.NewVersionUnsafe("0.666"),
} }
layers := []database.Layer{ layers := []services.Layer{
{ {
Name: "TestInsertLayer1", Name: "TestInsertLayer1",
}, },
{ {
Name: "TestInsertLayer2", Name: "TestInsertLayer2",
Parent: &database.Layer{Name: "TestInsertLayer1"}, Parent: &services.Layer{Name: "TestInsertLayer1"},
Namespace: &database.Namespace{Name: "TestInsertLayerNamespace1"}, Namespace: &services.Namespace{Name: "TestInsertLayerNamespace1"},
}, },
// This layer changes the namespace and adds Features. // This layer changes the namespace and adds Features.
{ {
Name: "TestInsertLayer3", Name: "TestInsertLayer3",
Parent: &database.Layer{Name: "TestInsertLayer2"}, Parent: &services.Layer{Name: "TestInsertLayer2"},
Namespace: &database.Namespace{Name: "TestInsertLayerNamespace2"}, Namespace: &services.Namespace{Name: "TestInsertLayerNamespace2"},
Features: []database.FeatureVersion{f1, f2, f3}, Features: []services.FeatureVersion{f1, f2, f3},
}, },
// This layer covers the case where the last layer doesn't provide any new Feature. // This layer covers the case where the last layer doesn't provide any new Feature.
{ {
Name: "TestInsertLayer4a", Name: "TestInsertLayer4a",
Parent: &database.Layer{Name: "TestInsertLayer3"}, Parent: &services.Layer{Name: "TestInsertLayer3"},
Features: []database.FeatureVersion{f1, f2, f3}, Features: []services.FeatureVersion{f1, f2, f3},
}, },
// This layer covers the case where the last layer provides Features. // This layer covers the case where the last layer provides Features.
// It also modifies the Namespace ("upgrade") but keeps some Features not upgraded, their // It also modifies the Namespace ("upgrade") but keeps some Features not upgraded, their
// Namespaces should then remain unchanged. // Namespaces should then remain unchanged.
{ {
Name: "TestInsertLayer4b", Name: "TestInsertLayer4b",
Parent: &database.Layer{Name: "TestInsertLayer3"}, Parent: &services.Layer{Name: "TestInsertLayer3"},
Namespace: &database.Namespace{Name: "TestInsertLayerNamespace3"}, Namespace: &services.Namespace{Name: "TestInsertLayerNamespace3"},
Features: []database.FeatureVersion{ Features: []services.FeatureVersion{
// Deletes TestInsertLayerFeature1. // Deletes TestInsertLayerFeature1.
// Keep TestInsertLayerFeature2 (old Namespace should be kept): // Keep TestInsertLayerFeature2 (old Namespace should be kept):
f4, f4,
@ -222,7 +223,7 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
} }
var err error var err error
retrievedLayers := make(map[string]database.Layer) retrievedLayers := make(map[string]services.Layer)
for _, layer := range layers { for _, layer := range layers {
if layer.Parent != nil { if layer.Parent != nil {
// Retrieve from database its parent and assign. // Retrieve from database its parent and assign.
@ -230,10 +231,10 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
layer.Parent = &parent layer.Parent = &parent
} }
err = datastore.InsertLayer(layer) err = ls.InsertLayer(layer)
assert.Nil(t, err) assert.Nil(t, err)
retrievedLayers[layer.Name], err = datastore.FindLayer(layer.Name, true, false) retrievedLayers[layer.Name], err = ls.FindLayer(layer.Name, true, false)
assert.Nil(t, err) assert.Nil(t, err)
} }
@ -260,35 +261,35 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
} }
} }
func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) { func testInsertLayerUpdate(t *testing.T, ls vulnerabilities.Service) {
f7 := database.FeatureVersion{ f7 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"}, Namespace: services.Namespace{Name: "TestInsertLayerNamespace3"},
Name: "TestInsertLayerFeature7", Name: "TestInsertLayerFeature7",
}, },
Version: types.NewVersionUnsafe("0.01"), Version: types.NewVersionUnsafe("0.01"),
} }
l3, _ := datastore.FindLayer("TestInsertLayer3", true, false) l3, _ := ls.FindLayer("TestInsertLayer3", true, false)
l3u := database.Layer{ l3u := services.Layer{
Name: l3.Name, Name: l3.Name,
Parent: l3.Parent, Parent: l3.Parent,
Namespace: &database.Namespace{Name: "TestInsertLayerNamespaceUpdated1"}, Namespace: &services.Namespace{Name: "TestInsertLayerNamespaceUpdated1"},
Features: []database.FeatureVersion{f7}, Features: []services.FeatureVersion{f7},
} }
l4u := database.Layer{ l4u := services.Layer{
Name: "TestInsertLayer4", Name: "TestInsertLayer4",
Parent: &database.Layer{Name: "TestInsertLayer3"}, Parent: &services.Layer{Name: "TestInsertLayer3"},
Features: []database.FeatureVersion{f7}, Features: []services.FeatureVersion{f7},
EngineVersion: 2, EngineVersion: 2,
} }
// Try to re-insert without increasing the EngineVersion. // Try to re-insert without increasing the EngineVersion.
err := datastore.InsertLayer(l3u) err := ls.InsertLayer(l3u)
assert.Nil(t, err) assert.Nil(t, err)
l3uf, err := datastore.FindLayer(l3u.Name, true, false) l3uf, err := ls.FindLayer(l3u.Name, true, false)
if assert.Nil(t, err) { if assert.Nil(t, err) {
assert.Equal(t, l3.Namespace.Name, l3uf.Namespace.Name) assert.Equal(t, l3.Namespace.Name, l3uf.Namespace.Name)
assert.Equal(t, l3.EngineVersion, l3uf.EngineVersion) assert.Equal(t, l3.EngineVersion, l3uf.EngineVersion)
@ -298,10 +299,10 @@ func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {
// Update layer l3. // Update layer l3.
// Verify that the Namespace, EngineVersion and FeatureVersions got updated. // Verify that the Namespace, EngineVersion and FeatureVersions got updated.
l3u.EngineVersion = 2 l3u.EngineVersion = 2
err = datastore.InsertLayer(l3u) err = ls.InsertLayer(l3u)
assert.Nil(t, err) assert.Nil(t, err)
l3uf, err = datastore.FindLayer(l3u.Name, true, false) l3uf, err = ls.FindLayer(l3u.Name, true, false)
if assert.Nil(t, err) { if assert.Nil(t, err) {
assert.Equal(t, l3u.Namespace.Name, l3uf.Namespace.Name) assert.Equal(t, l3u.Namespace.Name, l3uf.Namespace.Name)
assert.Equal(t, l3u.EngineVersion, l3uf.EngineVersion) assert.Equal(t, l3u.EngineVersion, l3uf.EngineVersion)
@ -314,10 +315,10 @@ func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {
// Verify that the Namespace got updated from its new Parent's, and also verify the // Verify that the Namespace got updated from its new Parent's, and also verify the
// EnginVersion and FeatureVersions. // EnginVersion and FeatureVersions.
l4u.Parent = &l3uf l4u.Parent = &l3uf
err = datastore.InsertLayer(l4u) err = ls.InsertLayer(l4u)
assert.Nil(t, err) assert.Nil(t, err)
l4uf, err := datastore.FindLayer(l3u.Name, true, false) l4uf, err := ls.FindLayer(l3u.Name, true, false)
if assert.Nil(t, err) { if assert.Nil(t, err) {
assert.Equal(t, l3u.Namespace.Name, l4uf.Namespace.Name) assert.Equal(t, l3u.Namespace.Name, l4uf.Namespace.Name)
assert.Equal(t, l4u.EngineVersion, l4uf.EngineVersion) assert.Equal(t, l4u.EngineVersion, l4uf.EngineVersion)
@ -327,24 +328,24 @@ func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {
} }
} }
func testInsertLayerDelete(t *testing.T, datastore database.Datastore) { func testInsertLayerDelete(t *testing.T, ls vulnerabilities.Service) {
err := datastore.DeleteLayer("TestInsertLayerX") err := ls.DeleteLayer("TestInsertLayerX")
assert.Equal(t, cerrors.ErrNotFound, err) assert.Equal(t, cerrors.ErrNotFound, err)
err = datastore.DeleteLayer("TestInsertLayer3") err = ls.DeleteLayer("TestInsertLayer3")
assert.Nil(t, err) assert.Nil(t, err)
_, err = datastore.FindLayer("TestInsertLayer3", false, false) _, err = ls.FindLayer("TestInsertLayer3", false, false)
assert.Equal(t, cerrors.ErrNotFound, err) assert.Equal(t, cerrors.ErrNotFound, err)
_, err = datastore.FindLayer("TestInsertLayer4a", false, false) _, err = ls.FindLayer("TestInsertLayer4a", false, false)
assert.Equal(t, cerrors.ErrNotFound, err) assert.Equal(t, cerrors.ErrNotFound, err)
_, err = datastore.FindLayer("TestInsertLayer4b", true, false) _, err = ls.FindLayer("TestInsertLayer4b", true, false)
assert.Equal(t, cerrors.ErrNotFound, err) assert.Equal(t, cerrors.ErrNotFound, err)
} }
func cmpFV(a, b database.FeatureVersion) bool { func cmpFV(a, b services.FeatureVersion) bool {
return a.Feature.Name == b.Feature.Name && return a.Feature.Name == b.Feature.Name &&
a.Feature.Namespace.Name == b.Feature.Namespace.Name && a.Feature.Namespace.Name == b.Feature.Namespace.Name &&
a.Version.String() == b.Version.String() a.Version.String() == b.Version.String()

View File

@ -17,11 +17,11 @@ package pgsql
import ( import (
"time" "time"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
) )
func (pgSQL *pgSQL) insertNamespace(namespace database.Namespace) (int, error) { func (pgSQL *pgSQL) insertNamespace(namespace services.Namespace) (int, error) {
if namespace.Name == "" { if namespace.Name == "" {
return 0, cerrors.NewBadRequestError("could not find/insert invalid Namespace") return 0, cerrors.NewBadRequestError("could not find/insert invalid Namespace")
} }
@ -50,7 +50,7 @@ func (pgSQL *pgSQL) insertNamespace(namespace database.Namespace) (int, error) {
return id, nil return id, nil
} }
func (pgSQL *pgSQL) ListNamespaces() (namespaces []database.Namespace, err error) { func (pgSQL *pgSQL) ListNamespaces() (namespaces []services.Namespace, err error) {
rows, err := pgSQL.Query(listNamespace) rows, err := pgSQL.Query(listNamespace)
if err != nil { if err != nil {
return namespaces, handleError("listNamespace", err) return namespaces, handleError("listNamespace", err)
@ -58,7 +58,7 @@ func (pgSQL *pgSQL) ListNamespaces() (namespaces []database.Namespace, err error
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var namespace database.Namespace var namespace services.Namespace
err = rows.Scan(&namespace.ID, &namespace.Name) err = rows.Scan(&namespace.ID, &namespace.Name)
if err != nil { if err != nil {

View File

@ -20,7 +20,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
) )
func TestInsertNamespace(t *testing.T) { func TestInsertNamespace(t *testing.T) {
@ -32,14 +32,14 @@ func TestInsertNamespace(t *testing.T) {
defer datastore.Close() defer datastore.Close()
// Invalid Namespace. // Invalid Namespace.
id0, err := datastore.insertNamespace(database.Namespace{}) id0, err := datastore.insertNamespace(services.Namespace{})
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Zero(t, id0) assert.Zero(t, id0)
// Insert Namespace and ensure we can find it. // Insert Namespace and ensure we can find it.
id1, err := datastore.insertNamespace(database.Namespace{Name: "TestInsertNamespace1"}) id1, err := datastore.insertNamespace(services.Namespace{Name: "TestInsertNamespace1"})
assert.Nil(t, err) assert.Nil(t, err)
id2, err := datastore.insertNamespace(database.Namespace{Name: "TestInsertNamespace1"}) id2, err := datastore.insertNamespace(services.Namespace{Name: "TestInsertNamespace1"})
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, id1, id2) assert.Equal(t, id1, id2)
} }

View File

@ -18,7 +18,7 @@ import (
"database/sql" "database/sql"
"time" "time"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/guregu/null/zero" "github.com/guregu/null/zero"
"github.com/pborman/uuid" "github.com/pborman/uuid"
@ -43,7 +43,7 @@ func createNotification(tx *sql.Tx, oldVulnerabilityID, newVulnerabilityID int)
// Get one available notification name (!locked && !deleted && (!notified || notified_but_timed-out)). // Get one available notification name (!locked && !deleted && (!notified || notified_but_timed-out)).
// Does not fill new/old vuln. // Does not fill new/old vuln.
func (pgSQL *pgSQL) GetAvailableNotification(renotifyInterval time.Duration) (database.VulnerabilityNotification, error) { func (pgSQL *pgSQL) GetAvailableNotification(renotifyInterval time.Duration) (services.VulnerabilityNotification, error) {
defer observeQueryTime("GetAvailableNotification", "all", time.Now()) defer observeQueryTime("GetAvailableNotification", "all", time.Now())
before := time.Now().Add(-renotifyInterval) before := time.Now().Add(-renotifyInterval)
@ -53,7 +53,7 @@ func (pgSQL *pgSQL) GetAvailableNotification(renotifyInterval time.Duration) (da
return notification, handleError("searchNotificationAvailable", err) return notification, handleError("searchNotificationAvailable", err)
} }
func (pgSQL *pgSQL) GetNotification(name string, limit int, page database.VulnerabilityNotificationPageNumber) (database.VulnerabilityNotification, database.VulnerabilityNotificationPageNumber, error) { func (pgSQL *pgSQL) GetNotification(name string, limit int, page services.VulnerabilityNotificationPageNumber) (services.VulnerabilityNotification, services.VulnerabilityNotificationPageNumber, error) {
defer observeQueryTime("GetNotification", "all", time.Now()) defer observeQueryTime("GetNotification", "all", time.Now())
// Get Notification. // Get Notification.
@ -86,8 +86,8 @@ func (pgSQL *pgSQL) GetNotification(name string, limit int, page database.Vulner
return notification, page, nil return notification, page, nil
} }
func (pgSQL *pgSQL) scanNotification(row *sql.Row, hasVulns bool) (database.VulnerabilityNotification, error) { func (pgSQL *pgSQL) scanNotification(row *sql.Row, hasVulns bool) (services.VulnerabilityNotification, error) {
var notification database.VulnerabilityNotification var notification services.VulnerabilityNotification
var created zero.Time var created zero.Time
var notified zero.Time var notified zero.Time
var deleted zero.Time var deleted zero.Time
@ -147,7 +147,7 @@ func (pgSQL *pgSQL) scanNotification(row *sql.Row, hasVulns bool) (database.Vuln
// Fills Vulnerability.LayersIntroducingVulnerability. // Fills Vulnerability.LayersIntroducingVulnerability.
// limit -1: won't do anything // limit -1: won't do anything
// limit 0: will just get the startID of the second page // limit 0: will just get the startID of the second page
func (pgSQL *pgSQL) loadLayerIntroducingVulnerability(vulnerability *database.Vulnerability, limit, startID int) (int, error) { func (pgSQL *pgSQL) loadLayerIntroducingVulnerability(vulnerability *services.Vulnerability, limit, startID int) (int, error) {
tf := time.Now() tf := time.Now()
if vulnerability == nil { if vulnerability == nil {
@ -170,9 +170,9 @@ func (pgSQL *pgSQL) loadLayerIntroducingVulnerability(vulnerability *database.Vu
} }
defer rows.Close() defer rows.Close()
var layers []database.Layer var layers []services.Layer
for rows.Next() { for rows.Next() {
var layer database.Layer var layer services.Layer
if err := rows.Scan(&layer.ID, &layer.Name); err != nil { if err := rows.Scan(&layer.ID, &layer.Name); err != nil {
return -1, handleError("searchNotificationLayerIntroducingVulnerability.Scan()", err) return -1, handleError("searchNotificationLayerIntroducingVulnerability.Scan()", err)

View File

@ -20,7 +20,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
) )
@ -38,19 +38,19 @@ func TestNotification(t *testing.T) {
assert.Equal(t, cerrors.ErrNotFound, err) assert.Equal(t, cerrors.ErrNotFound, err)
// Create some data. // Create some data.
f1 := database.Feature{ f1 := services.Feature{
Name: "TestNotificationFeature1", Name: "TestNotificationFeature1",
Namespace: database.Namespace{Name: "TestNotificationNamespace1"}, Namespace: services.Namespace{Name: "TestNotificationNamespace1"},
} }
f2 := database.Feature{ f2 := services.Feature{
Name: "TestNotificationFeature2", Name: "TestNotificationFeature2",
Namespace: database.Namespace{Name: "TestNotificationNamespace1"}, Namespace: services.Namespace{Name: "TestNotificationNamespace1"},
} }
l1 := database.Layer{ l1 := services.Layer{
Name: "TestNotificationLayer1", Name: "TestNotificationLayer1",
Features: []database.FeatureVersion{ Features: []services.FeatureVersion{
{ {
Feature: f1, Feature: f1,
Version: types.NewVersionUnsafe("0.1"), Version: types.NewVersionUnsafe("0.1"),
@ -58,9 +58,9 @@ func TestNotification(t *testing.T) {
}, },
} }
l2 := database.Layer{ l2 := services.Layer{
Name: "TestNotificationLayer2", Name: "TestNotificationLayer2",
Features: []database.FeatureVersion{ Features: []services.FeatureVersion{
{ {
Feature: f1, Feature: f1,
Version: types.NewVersionUnsafe("0.2"), Version: types.NewVersionUnsafe("0.2"),
@ -68,9 +68,9 @@ func TestNotification(t *testing.T) {
}, },
} }
l3 := database.Layer{ l3 := services.Layer{
Name: "TestNotificationLayer3", Name: "TestNotificationLayer3",
Features: []database.FeatureVersion{ Features: []services.FeatureVersion{
{ {
Feature: f1, Feature: f1,
Version: types.NewVersionUnsafe("0.3"), Version: types.NewVersionUnsafe("0.3"),
@ -78,9 +78,9 @@ func TestNotification(t *testing.T) {
}, },
} }
l4 := database.Layer{ l4 := services.Layer{
Name: "TestNotificationLayer4", Name: "TestNotificationLayer4",
Features: []database.FeatureVersion{ Features: []services.FeatureVersion{
{ {
Feature: f2, Feature: f2,
Version: types.NewVersionUnsafe("0.1"), Version: types.NewVersionUnsafe("0.1"),
@ -96,13 +96,13 @@ func TestNotification(t *testing.T) {
} }
// Insert a new vulnerability that is introduced by three layers. // Insert a new vulnerability that is introduced by three layers.
v1 := database.Vulnerability{ v1 := services.Vulnerability{
Name: "TestNotificationVulnerability1", Name: "TestNotificationVulnerability1",
Namespace: f1.Namespace, Namespace: f1.Namespace,
Description: "TestNotificationDescription1", Description: "TestNotificationDescription1",
Link: "TestNotificationLink1", Link: "TestNotificationLink1",
Severity: "Unknown", Severity: "Unknown",
FixedIn: []database.FeatureVersion{ FixedIn: []services.FeatureVersion{
{ {
Feature: f1, Feature: f1,
Version: types.NewVersionUnsafe("1.0"), Version: types.NewVersionUnsafe("1.0"),
@ -129,9 +129,9 @@ func TestNotification(t *testing.T) {
} }
// Get notification. // Get notification.
filledNotification, nextPage, err := datastore.GetNotification(notification.Name, 2, database.VulnerabilityNotificationFirstPage) filledNotification, nextPage, err := datastore.GetNotification(notification.Name, 2, services.VulnerabilityNotificationFirstPage)
if assert.Nil(t, err) { if assert.Nil(t, err) {
assert.NotEqual(t, database.NoVulnerabilityNotificationPage, nextPage) assert.NotEqual(t, services.NoVulnerabilityNotificationPage, nextPage)
assert.Nil(t, filledNotification.OldVulnerability) assert.Nil(t, filledNotification.OldVulnerability)
if assert.NotNil(t, filledNotification.NewVulnerability) { if assert.NotNil(t, filledNotification.NewVulnerability) {
@ -143,7 +143,7 @@ func TestNotification(t *testing.T) {
// Get second page. // Get second page.
filledNotification, nextPage, err = datastore.GetNotification(notification.Name, 2, nextPage) filledNotification, nextPage, err = datastore.GetNotification(notification.Name, 2, nextPage)
if assert.Nil(t, err) { if assert.Nil(t, err) {
assert.Equal(t, database.NoVulnerabilityNotificationPage, nextPage) assert.Equal(t, services.NoVulnerabilityNotificationPage, nextPage)
assert.Nil(t, filledNotification.OldVulnerability) assert.Nil(t, filledNotification.OldVulnerability)
if assert.NotNil(t, filledNotification.NewVulnerability) { if assert.NotNil(t, filledNotification.NewVulnerability) {
@ -162,7 +162,7 @@ func TestNotification(t *testing.T) {
// Update a vulnerability and ensure that the old/new vulnerabilities are correct. // Update a vulnerability and ensure that the old/new vulnerabilities are correct.
v1b := v1 v1b := v1
v1b.Severity = types.High v1b.Severity = types.High
v1b.FixedIn = []database.FeatureVersion{ v1b.FixedIn = []services.FeatureVersion{
{ {
Feature: f1, Feature: f1,
Version: types.MinVersion, Version: types.MinVersion,
@ -179,7 +179,7 @@ func TestNotification(t *testing.T) {
assert.NotEmpty(t, notification.Name) assert.NotEmpty(t, notification.Name)
if assert.Nil(t, err) && assert.NotEmpty(t, notification.Name) { if assert.Nil(t, err) && assert.NotEmpty(t, notification.Name) {
filledNotification, nextPage, err := datastore.GetNotification(notification.Name, 2, database.VulnerabilityNotificationFirstPage) filledNotification, nextPage, err := datastore.GetNotification(notification.Name, 2, services.VulnerabilityNotificationFirstPage)
if assert.Nil(t, err) { if assert.Nil(t, err) {
if assert.NotNil(t, filledNotification.OldVulnerability) { if assert.NotNil(t, filledNotification.OldVulnerability) {
assert.Equal(t, v1.Name, filledNotification.OldVulnerability.Name) assert.Equal(t, v1.Name, filledNotification.OldVulnerability.Name)
@ -207,7 +207,7 @@ func TestNotification(t *testing.T) {
assert.NotEmpty(t, notification.Name) assert.NotEmpty(t, notification.Name)
if assert.Nil(t, err) && assert.NotEmpty(t, notification.Name) { if assert.Nil(t, err) && assert.NotEmpty(t, notification.Name) {
filledNotification, _, err := datastore.GetNotification(notification.Name, 2, database.VulnerabilityNotificationFirstPage) filledNotification, _, err := datastore.GetNotification(notification.Name, 2, services.VulnerabilityNotificationFirstPage)
if assert.Nil(t, err) { if assert.Nil(t, err) {
assert.Nil(t, filledNotification.NewVulnerability) assert.Nil(t, filledNotification.NewVulnerability)

View File

@ -27,13 +27,17 @@ import (
"bitbucket.org/liamstask/goose/lib/goose" "bitbucket.org/liamstask/goose/lib/goose"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
"github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
"github.com/coreos/clair/config" "github.com/coreos/clair/config"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/services/keyvalue"
"github.com/coreos/clair/services/locks"
"github.com/coreos/clair/services/notifications"
"github.com/coreos/clair/services/vulnerabilities"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
) )
@ -74,7 +78,19 @@ func init() {
prometheus.MustRegister(promQueryDurationMilliseconds) prometheus.MustRegister(promQueryDurationMilliseconds)
prometheus.MustRegister(promConcurrentLockVAFV) prometheus.MustRegister(promConcurrentLockVAFV)
database.Register("pgsql", openDatabase) // The return types must match exactly because Go doesn't seem to allow covariance.
locks.Register("pgsql", func(cfg config.RegistrableComponentConfig) (locks.Service, error) {
return openDatabase(cfg)
})
keyvalue.Register("pgsql", func(cfg config.RegistrableComponentConfig) (keyvalue.Service, error) {
return openDatabase(cfg)
})
vulnerabilities.Register("pgsql", func(cfg config.RegistrableComponentConfig) (vulnerabilities.Service, error) {
return openDatabase(cfg)
})
notifications.Register("pgsql", func(cfg config.RegistrableComponentConfig) (notifications.Service, error) {
return openDatabase(cfg)
})
} }
type Queryer interface { type Queryer interface {
@ -119,7 +135,7 @@ type Config struct {
// It immediately every necessary migrations. If ManageDatabaseLifecycle is specified, // It immediately every necessary migrations. If ManageDatabaseLifecycle is specified,
// the database will be created first. If FixturePath is specified, every SQL queries that are // the database will be created first. If FixturePath is specified, every SQL queries that are
// present insides will be executed. // present insides will be executed.
func openDatabase(registrableComponentConfig config.RegistrableComponentConfig) (database.Datastore, error) { func openDatabase(registrableComponentConfig config.RegistrableComponentConfig) (*pgSQL, error) {
var pg pgSQL var pg pgSQL
var err error var err error
@ -306,7 +322,7 @@ func handleError(desc string, err error) error {
promErrorsTotal.WithLabelValues(desc).Inc() promErrorsTotal.WithLabelValues(desc).Inc()
if _, o := err.(*pq.Error); o || err == sql.ErrTxDone || strings.HasPrefix(err.Error(), "sql:") { if _, o := err.(*pq.Error); o || err == sql.ErrTxDone || strings.HasPrefix(err.Error(), "sql:") {
return database.ErrBackendException return services.ErrBackendException
} }
return err return err

View File

@ -26,12 +26,7 @@ import (
) )
func openDatabaseForTest(testName string, loadFixture bool) (*pgSQL, error) { func openDatabaseForTest(testName string, loadFixture bool) (*pgSQL, error) {
ds, err := openDatabase(generateTestConfig(testName, loadFixture)) return openDatabase(generateTestConfig(testName, loadFixture))
if err != nil {
return nil, err
}
datastore := ds.(*pgSQL)
return datastore, nil
} }
func generateTestConfig(testName string, loadFixture bool) config.RegistrableComponentConfig { func generateTestConfig(testName string, loadFixture bool) config.RegistrableComponentConfig {

View File

@ -21,14 +21,14 @@ import (
"reflect" "reflect"
"time" "time"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
"github.com/guregu/null/zero" "github.com/guregu/null/zero"
) )
func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, startID int) ([]database.Vulnerability, int, error) { func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, startID int) ([]services.Vulnerability, int, error) {
defer observeQueryTime("listVulnerabilities", "all", time.Now()) defer observeQueryTime("listVulnerabilities", "all", time.Now())
// Query Namespace. // Query Namespace.
@ -48,12 +48,12 @@ func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, startID
} }
defer rows.Close() defer rows.Close()
var vulns []database.Vulnerability var vulns []services.Vulnerability
nextID := -1 nextID := -1
size := 0 size := 0
// Scan query. // Scan query.
for rows.Next() { for rows.Next() {
var vulnerability database.Vulnerability var vulnerability services.Vulnerability
err := rows.Scan( err := rows.Scan(
&vulnerability.ID, &vulnerability.ID,
@ -83,11 +83,11 @@ func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, startID
return vulns, nextID, nil return vulns, nextID, nil
} }
func (pgSQL *pgSQL) FindVulnerability(namespaceName, name string) (database.Vulnerability, error) { func (pgSQL *pgSQL) FindVulnerability(namespaceName, name string) (services.Vulnerability, error) {
return findVulnerability(pgSQL, namespaceName, name, false) return findVulnerability(pgSQL, namespaceName, name, false)
} }
func findVulnerability(queryer Queryer, namespaceName, name string, forUpdate bool) (database.Vulnerability, error) { func findVulnerability(queryer Queryer, namespaceName, name string, forUpdate bool) (services.Vulnerability, error) {
defer observeQueryTime("findVulnerability", "all", time.Now()) defer observeQueryTime("findVulnerability", "all", time.Now())
queryName := "searchVulnerabilityBase+searchVulnerabilityByNamespaceAndName" queryName := "searchVulnerabilityBase+searchVulnerabilityByNamespaceAndName"
@ -100,7 +100,7 @@ func findVulnerability(queryer Queryer, namespaceName, name string, forUpdate bo
return scanVulnerability(queryer, queryName, queryer.QueryRow(query, namespaceName, name)) return scanVulnerability(queryer, queryName, queryer.QueryRow(query, namespaceName, name))
} }
func (pgSQL *pgSQL) findVulnerabilityByIDWithDeleted(id int) (database.Vulnerability, error) { func (pgSQL *pgSQL) findVulnerabilityByIDWithDeleted(id int) (services.Vulnerability, error) {
defer observeQueryTime("findVulnerabilityByIDWithDeleted", "all", time.Now()) defer observeQueryTime("findVulnerabilityByIDWithDeleted", "all", time.Now())
queryName := "searchVulnerabilityBase+searchVulnerabilityByID" queryName := "searchVulnerabilityBase+searchVulnerabilityByID"
@ -109,8 +109,8 @@ func (pgSQL *pgSQL) findVulnerabilityByIDWithDeleted(id int) (database.Vulnerabi
return scanVulnerability(pgSQL, queryName, pgSQL.QueryRow(query, id)) return scanVulnerability(pgSQL, queryName, pgSQL.QueryRow(query, id))
} }
func scanVulnerability(queryer Queryer, queryName string, vulnerabilityRow *sql.Row) (database.Vulnerability, error) { func scanVulnerability(queryer Queryer, queryName string, vulnerabilityRow *sql.Row) (services.Vulnerability, error) {
var vulnerability database.Vulnerability var vulnerability services.Vulnerability
err := vulnerabilityRow.Scan( err := vulnerabilityRow.Scan(
&vulnerability.ID, &vulnerability.ID,
@ -156,10 +156,10 @@ func scanVulnerability(queryer Queryer, queryName string, vulnerabilityRow *sql.
if !featureVersionID.IsZero() { if !featureVersionID.IsZero() {
// Note that the ID we fill in featureVersion is actually a Feature ID, and not // Note that the ID we fill in featureVersion is actually a Feature ID, and not
// a FeatureVersion ID. // a FeatureVersion ID.
featureVersion := database.FeatureVersion{ featureVersion := services.FeatureVersion{
Model: database.Model{ID: int(featureVersionID.Int64)}, Model: services.Model{ID: int(featureVersionID.Int64)},
Feature: database.Feature{ Feature: services.Feature{
Model: database.Model{ID: int(featureVersionID.Int64)}, Model: services.Model{ID: int(featureVersionID.Int64)},
Namespace: vulnerability.Namespace, Namespace: vulnerability.Namespace,
Name: featureVersionFeatureName.String, Name: featureVersionFeatureName.String,
}, },
@ -178,7 +178,7 @@ func scanVulnerability(queryer Queryer, queryName string, vulnerabilityRow *sql.
// FixedIn.Namespace are not necessary, they are overwritten by the vuln. // FixedIn.Namespace are not necessary, they are overwritten by the vuln.
// By setting the fixed version to minVersion, we can say that the vuln does'nt affect anymore. // By setting the fixed version to minVersion, we can say that the vuln does'nt affect anymore.
func (pgSQL *pgSQL) InsertVulnerabilities(vulnerabilities []database.Vulnerability, generateNotifications bool) error { func (pgSQL *pgSQL) InsertVulnerabilities(vulnerabilities []services.Vulnerability, generateNotifications bool) error {
for _, vulnerability := range vulnerabilities { for _, vulnerability := range vulnerabilities {
err := pgSQL.insertVulnerability(vulnerability, false, generateNotifications) err := pgSQL.insertVulnerability(vulnerability, false, generateNotifications)
if err != nil { if err != nil {
@ -189,7 +189,7 @@ func (pgSQL *pgSQL) InsertVulnerabilities(vulnerabilities []database.Vulnerabili
return nil return nil
} }
func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, onlyFixedIn, generateNotification bool) error { func (pgSQL *pgSQL) insertVulnerability(vulnerability services.Vulnerability, onlyFixedIn, generateNotification bool) error {
tf := time.Now() tf := time.Now()
// Verify parameters // Verify parameters
@ -272,7 +272,7 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on
} else { } else {
// The vulnerability is new, we don't want to have any types.MinVersion as they are only used // The vulnerability is new, we don't want to have any types.MinVersion as they are only used
// for diffing existing vulnerabilities. // for diffing existing vulnerabilities.
var fixedIn []database.FeatureVersion var fixedIn []services.FeatureVersion
for _, fv := range vulnerability.FixedIn { for _, fv := range vulnerability.FixedIn {
if fv.Version != types.MinVersion { if fv.Version != types.MinVersion {
fixedIn = append(fixedIn, fv) fixedIn = append(fixedIn, fv)
@ -328,19 +328,19 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on
return nil return nil
} }
// castMetadata marshals the given database.MetadataMap and unmarshals it again to make sure that // castMetadata marshals the given services.MetadataMap and unmarshals it again to make sure that
// everything has the interface{} type. // everything has the interface{} type.
// It is required when comparing crafted MetadataMap against MetadataMap that we get from the // It is required when comparing crafted MetadataMap against MetadataMap that we get from the
// database. // database.
func castMetadata(m database.MetadataMap) database.MetadataMap { func castMetadata(m services.MetadataMap) services.MetadataMap {
c := make(database.MetadataMap) c := make(services.MetadataMap)
j, _ := json.Marshal(m) j, _ := json.Marshal(m)
json.Unmarshal(j, &c) json.Unmarshal(j, &c)
return c return c
} }
// applyFixedInDiff applies a FeatureVersion diff on a FeatureVersion list and returns the result. // applyFixedInDiff applies a FeatureVersion diff on a FeatureVersion list and returns the result.
func applyFixedInDiff(currentList, diff []database.FeatureVersion) ([]database.FeatureVersion, bool) { func applyFixedInDiff(currentList, diff []services.FeatureVersion) ([]services.FeatureVersion, bool) {
currentMap, currentNames := createFeatureVersionNameMap(currentList) currentMap, currentNames := createFeatureVersionNameMap(currentList)
diffMap, diffNames := createFeatureVersionNameMap(diff) diffMap, diffNames := createFeatureVersionNameMap(diff)
@ -375,7 +375,7 @@ func applyFixedInDiff(currentList, diff []database.FeatureVersion) ([]database.F
} }
// Convert currentMap to a slice and return it. // Convert currentMap to a slice and return it.
var newList []database.FeatureVersion var newList []services.FeatureVersion
for _, fv := range currentMap { for _, fv := range currentMap {
newList = append(newList, fv) newList = append(newList, fv)
} }
@ -383,8 +383,8 @@ func applyFixedInDiff(currentList, diff []database.FeatureVersion) ([]database.F
return newList, different return newList, different
} }
func createFeatureVersionNameMap(features []database.FeatureVersion) (map[string]database.FeatureVersion, []string) { func createFeatureVersionNameMap(features []services.FeatureVersion) (map[string]services.FeatureVersion, []string) {
m := make(map[string]database.FeatureVersion, 0) m := make(map[string]services.FeatureVersion, 0)
s := make([]string, 0, len(features)) s := make([]string, 0, len(features))
for i := 0; i < len(features); i++ { for i := 0; i < len(features); i++ {
@ -397,16 +397,16 @@ func createFeatureVersionNameMap(features []database.FeatureVersion) (map[string
} }
// insertVulnerabilityFixedInFeatureVersions populates Vulnerability_FixedIn_Feature for the given // insertVulnerabilityFixedInFeatureVersions populates Vulnerability_FixedIn_Feature for the given
// vulnerability with the specified database.FeatureVersion list and uses // vulnerability with the specified services.FeatureVersion list and uses
// linkVulnerabilityToFeatureVersions to propagate the changes on Vulnerability_FixedIn_Feature to // linkVulnerabilityToFeatureVersions to propagate the changes on Vulnerability_FixedIn_Feature to
// Vulnerability_Affects_FeatureVersion. // Vulnerability_Affects_FeatureVersion.
func (pgSQL *pgSQL) insertVulnerabilityFixedInFeatureVersions(tx *sql.Tx, vulnerabilityID int, fixedIn []database.FeatureVersion) error { func (pgSQL *pgSQL) insertVulnerabilityFixedInFeatureVersions(tx *sql.Tx, vulnerabilityID int, fixedIn []services.FeatureVersion) error {
defer observeQueryTime("insertVulnerabilityFixedInFeatureVersions", "all", time.Now()) defer observeQueryTime("insertVulnerabilityFixedInFeatureVersions", "all", time.Now())
// Insert or find the Features. // Insert or find the Features.
// TODO(Quentin-M): Batch me. // TODO(Quentin-M): Batch me.
var err error var err error
var features []*database.Feature var features []*services.Feature
for i := 0; i < len(fixedIn); i++ { for i := 0; i < len(fixedIn); i++ {
features = append(features, &fixedIn[i].Feature) features = append(features, &fixedIn[i].Feature)
} }
@ -464,9 +464,9 @@ func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID,
} }
defer rows.Close() defer rows.Close()
var affecteds []database.FeatureVersion var affecteds []services.FeatureVersion
for rows.Next() { for rows.Next() {
var affected database.FeatureVersion var affected services.FeatureVersion
err := rows.Scan(&affected.ID, &affected.Version) err := rows.Scan(&affected.ID, &affected.Version)
if err != nil { if err != nil {
@ -497,12 +497,12 @@ func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID,
return nil return nil
} }
func (pgSQL *pgSQL) InsertVulnerabilityFixes(vulnerabilityNamespace, vulnerabilityName string, fixes []database.FeatureVersion) error { func (pgSQL *pgSQL) InsertVulnerabilityFixes(vulnerabilityNamespace, vulnerabilityName string, fixes []services.FeatureVersion) error {
defer observeQueryTime("InsertVulnerabilityFixes", "all", time.Now()) defer observeQueryTime("InsertVulnerabilityFixes", "all", time.Now())
v := database.Vulnerability{ v := services.Vulnerability{
Name: vulnerabilityName, Name: vulnerabilityName,
Namespace: database.Namespace{ Namespace: services.Namespace{
Name: vulnerabilityNamespace, Name: vulnerabilityNamespace,
}, },
FixedIn: fixes, FixedIn: fixes,
@ -514,16 +514,16 @@ func (pgSQL *pgSQL) InsertVulnerabilityFixes(vulnerabilityNamespace, vulnerabili
func (pgSQL *pgSQL) DeleteVulnerabilityFix(vulnerabilityNamespace, vulnerabilityName, featureName string) error { func (pgSQL *pgSQL) DeleteVulnerabilityFix(vulnerabilityNamespace, vulnerabilityName, featureName string) error {
defer observeQueryTime("DeleteVulnerabilityFix", "all", time.Now()) defer observeQueryTime("DeleteVulnerabilityFix", "all", time.Now())
v := database.Vulnerability{ v := services.Vulnerability{
Name: vulnerabilityName, Name: vulnerabilityName,
Namespace: database.Namespace{ Namespace: services.Namespace{
Name: vulnerabilityNamespace, Name: vulnerabilityNamespace,
}, },
FixedIn: []database.FeatureVersion{ FixedIn: []services.FeatureVersion{
{ {
Feature: database.Feature{ Feature: services.Feature{
Name: featureName, Name: featureName,
Namespace: database.Namespace{ Namespace: services.Namespace{
Name: vulnerabilityNamespace, Name: vulnerabilityNamespace,
}, },
}, },

View File

@ -20,7 +20,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
) )
@ -38,19 +38,19 @@ func TestFindVulnerability(t *testing.T) {
assert.Equal(t, cerrors.ErrNotFound, err) assert.Equal(t, cerrors.ErrNotFound, err)
// Find a normal vulnerability. // Find a normal vulnerability.
v1 := database.Vulnerability{ v1 := services.Vulnerability{
Name: "CVE-OPENSSL-1-DEB7", Name: "CVE-OPENSSL-1-DEB7",
Description: "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0", Description: "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0",
Link: "http://google.com/#q=CVE-OPENSSL-1-DEB7", Link: "http://google.com/#q=CVE-OPENSSL-1-DEB7",
Severity: types.High, Severity: types.High,
Namespace: database.Namespace{Name: "debian:7"}, Namespace: services.Namespace{Name: "debian:7"},
FixedIn: []database.FeatureVersion{ FixedIn: []services.FeatureVersion{
{ {
Feature: database.Feature{Name: "openssl"}, Feature: services.Feature{Name: "openssl"},
Version: types.NewVersionUnsafe("2.0"), Version: types.NewVersionUnsafe("2.0"),
}, },
{ {
Feature: database.Feature{Name: "libssl"}, Feature: services.Feature{Name: "libssl"},
Version: types.NewVersionUnsafe("1.9-abc"), Version: types.NewVersionUnsafe("1.9-abc"),
}, },
}, },
@ -62,10 +62,10 @@ func TestFindVulnerability(t *testing.T) {
} }
// Find a vulnerability that has no link, no severity and no FixedIn. // Find a vulnerability that has no link, no severity and no FixedIn.
v2 := database.Vulnerability{ v2 := services.Vulnerability{
Name: "CVE-NOPE", Name: "CVE-NOPE",
Description: "A vulnerability affecting nothing", Description: "A vulnerability affecting nothing",
Namespace: database.Namespace{Name: "debian:7"}, Namespace: services.Namespace{Name: "debian:7"},
Severity: types.Unknown, Severity: types.Unknown,
} }
@ -106,93 +106,93 @@ func TestInsertVulnerability(t *testing.T) {
defer datastore.Close() defer datastore.Close()
// Create some data. // Create some data.
n1 := database.Namespace{Name: "TestInsertVulnerabilityNamespace1"} n1 := services.Namespace{Name: "TestInsertVulnerabilityNamespace1"}
n2 := database.Namespace{Name: "TestInsertVulnerabilityNamespace2"} n2 := services.Namespace{Name: "TestInsertVulnerabilityNamespace2"}
f1 := database.FeatureVersion{ f1 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Name: "TestInsertVulnerabilityFeatureVersion1", Name: "TestInsertVulnerabilityFeatureVersion1",
Namespace: n1, Namespace: n1,
}, },
Version: types.NewVersionUnsafe("1.0"), Version: types.NewVersionUnsafe("1.0"),
} }
f2 := database.FeatureVersion{ f2 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Name: "TestInsertVulnerabilityFeatureVersion1", Name: "TestInsertVulnerabilityFeatureVersion1",
Namespace: n2, Namespace: n2,
}, },
Version: types.NewVersionUnsafe("1.0"), Version: types.NewVersionUnsafe("1.0"),
} }
f3 := database.FeatureVersion{ f3 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Name: "TestInsertVulnerabilityFeatureVersion2", Name: "TestInsertVulnerabilityFeatureVersion2",
}, },
Version: types.MaxVersion, Version: types.MaxVersion,
} }
f4 := database.FeatureVersion{ f4 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Name: "TestInsertVulnerabilityFeatureVersion2", Name: "TestInsertVulnerabilityFeatureVersion2",
}, },
Version: types.NewVersionUnsafe("1.4"), Version: types.NewVersionUnsafe("1.4"),
} }
f5 := database.FeatureVersion{ f5 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Name: "TestInsertVulnerabilityFeatureVersion3", Name: "TestInsertVulnerabilityFeatureVersion3",
}, },
Version: types.NewVersionUnsafe("1.5"), Version: types.NewVersionUnsafe("1.5"),
} }
f6 := database.FeatureVersion{ f6 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Name: "TestInsertVulnerabilityFeatureVersion4", Name: "TestInsertVulnerabilityFeatureVersion4",
}, },
Version: types.NewVersionUnsafe("0.1"), Version: types.NewVersionUnsafe("0.1"),
} }
f7 := database.FeatureVersion{ f7 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Name: "TestInsertVulnerabilityFeatureVersion5", Name: "TestInsertVulnerabilityFeatureVersion5",
}, },
Version: types.MaxVersion, Version: types.MaxVersion,
} }
f8 := database.FeatureVersion{ f8 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Name: "TestInsertVulnerabilityFeatureVersion5", Name: "TestInsertVulnerabilityFeatureVersion5",
}, },
Version: types.MinVersion, Version: types.MinVersion,
} }
// Insert invalid vulnerabilities. // Insert invalid vulnerabilities.
for _, vulnerability := range []database.Vulnerability{ for _, vulnerability := range []services.Vulnerability{
{ {
Name: "", Name: "",
Namespace: n1, Namespace: n1,
FixedIn: []database.FeatureVersion{f1}, FixedIn: []services.FeatureVersion{f1},
Severity: types.Unknown, Severity: types.Unknown,
}, },
{ {
Name: "TestInsertVulnerability0", Name: "TestInsertVulnerability0",
Namespace: database.Namespace{}, Namespace: services.Namespace{},
FixedIn: []database.FeatureVersion{f1}, FixedIn: []services.FeatureVersion{f1},
Severity: types.Unknown, Severity: types.Unknown,
}, },
{ {
Name: "TestInsertVulnerability0-", Name: "TestInsertVulnerability0-",
Namespace: database.Namespace{}, Namespace: services.Namespace{},
FixedIn: []database.FeatureVersion{f1}, FixedIn: []services.FeatureVersion{f1},
}, },
{ {
Name: "TestInsertVulnerability0", Name: "TestInsertVulnerability0",
Namespace: n1, Namespace: n1,
FixedIn: []database.FeatureVersion{f1}, FixedIn: []services.FeatureVersion{f1},
Severity: types.Priority(""), Severity: types.Priority(""),
}, },
{ {
Name: "TestInsertVulnerability0", Name: "TestInsertVulnerability0",
Namespace: n1, Namespace: n1,
FixedIn: []database.FeatureVersion{f2}, FixedIn: []services.FeatureVersion{f2},
Severity: types.Unknown, Severity: types.Unknown,
}, },
} { } {
err := datastore.InsertVulnerabilities([]database.Vulnerability{vulnerability}, true) err := datastore.InsertVulnerabilities([]services.Vulnerability{vulnerability}, true)
assert.Error(t, err) assert.Error(t, err)
} }
@ -205,16 +205,16 @@ func TestInsertVulnerability(t *testing.T) {
Test: "TestInsertVulnerabilityMetadataValue1", Test: "TestInsertVulnerabilityMetadataValue1",
} }
v1 := database.Vulnerability{ v1 := services.Vulnerability{
Name: "TestInsertVulnerability1", Name: "TestInsertVulnerability1",
Namespace: n1, Namespace: n1,
FixedIn: []database.FeatureVersion{f1, f3, f6, f7}, FixedIn: []services.FeatureVersion{f1, f3, f6, f7},
Severity: types.Low, Severity: types.Low,
Description: "TestInsertVulnerabilityDescription1", Description: "TestInsertVulnerabilityDescription1",
Link: "TestInsertVulnerabilityLink1", Link: "TestInsertVulnerabilityLink1",
Metadata: v1meta, Metadata: v1meta,
} }
err = datastore.InsertVulnerabilities([]database.Vulnerability{v1}, true) err = datastore.InsertVulnerabilities([]services.Vulnerability{v1}, true)
if assert.Nil(t, err) { if assert.Nil(t, err) {
v1f, err := datastore.FindVulnerability(n1.Name, v1.Name) v1f, err := datastore.FindVulnerability(n1.Name, v1.Name)
if assert.Nil(t, err) { if assert.Nil(t, err) {
@ -228,9 +228,9 @@ func TestInsertVulnerability(t *testing.T) {
v1.Severity = types.High v1.Severity = types.High
// Update f3 in f4, add fixed in f5, add fixed in f6 which already exists, removes fixed in f7 by // Update f3 in f4, add fixed in f5, add fixed in f6 which already exists, removes fixed in f7 by
// adding f8 which is f7 but with MinVersion. // adding f8 which is f7 but with MinVersion.
v1.FixedIn = []database.FeatureVersion{f4, f5, f6, f8} v1.FixedIn = []services.FeatureVersion{f4, f5, f6, f8}
err = datastore.InsertVulnerabilities([]database.Vulnerability{v1}, true) err = datastore.InsertVulnerabilities([]services.Vulnerability{v1}, true)
if assert.Nil(t, err) { if assert.Nil(t, err) {
v1f, err := datastore.FindVulnerability(n1.Name, v1.Name) v1f, err := datastore.FindVulnerability(n1.Name, v1.Name)
if assert.Nil(t, err) { if assert.Nil(t, err) {
@ -250,7 +250,7 @@ func TestInsertVulnerability(t *testing.T) {
} }
} }
func equalsVuln(t *testing.T, expected, actual *database.Vulnerability) { func equalsVuln(t *testing.T, expected, actual *services.Vulnerability) {
assert.Equal(t, expected.Name, actual.Name) assert.Equal(t, expected.Name, actual.Name)
assert.Equal(t, expected.Namespace.Name, actual.Namespace.Name) assert.Equal(t, expected.Namespace.Name, actual.Namespace.Name)
assert.Equal(t, expected.Description, actual.Description) assert.Equal(t, expected.Description, actual.Description)

View File

@ -25,7 +25,9 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/coreos/clair/config" "github.com/coreos/clair/config"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/services/locks"
"github.com/coreos/clair/services/notifications"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
) )
@ -59,7 +61,7 @@ type Notifier interface {
// It returns whether the notifier is enabled or not. // It returns whether the notifier is enabled or not.
Configure(*config.NotifierConfig) (bool, error) Configure(*config.NotifierConfig) (bool, error)
// Send informs the existence of the specified notification. // Send informs the existence of the specified notification.
Send(notification database.VulnerabilityNotification) error Send(notification services.VulnerabilityNotification) error
} }
func init() { func init() {
@ -87,7 +89,7 @@ func RegisterNotifier(name string, n Notifier) {
} }
// Run starts the Notifier service. // Run starts the Notifier service.
func Run(config *config.NotifierConfig, datastore database.Datastore, stopper *utils.Stopper) { func Run(config *config.NotifierConfig, ls locks.Service, ns notifications.Service, stopper *utils.Stopper) {
defer stopper.End() defer stopper.End()
// Configure registered notifiers. // Configure registered notifiers.
@ -113,7 +115,7 @@ func Run(config *config.NotifierConfig, datastore database.Datastore, stopper *u
for running := true; running; { for running := true; running; {
// Find task. // Find task.
notification := findTask(datastore, config.RenotifyInterval, whoAmI, stopper) notification := findTask(ls, ns, config.RenotifyInterval, whoAmI, stopper)
if notification == nil { if notification == nil {
// Interrupted while finding a task, Clair is stopping. // Interrupted while finding a task, Clair is stopping.
break break
@ -125,12 +127,12 @@ func Run(config *config.NotifierConfig, datastore database.Datastore, stopper *u
success, interrupted := handleTask(*notification, stopper, config.Attempts) success, interrupted := handleTask(*notification, stopper, config.Attempts)
if success { if success {
utils.PrometheusObserveTimeMilliseconds(promNotifierLatencyMilliseconds, notification.Created) utils.PrometheusObserveTimeMilliseconds(promNotifierLatencyMilliseconds, notification.Created)
datastore.SetNotificationNotified(notification.Name) ns.SetNotificationNotified(notification.Name)
} }
if interrupted { if interrupted {
running = false running = false
} }
datastore.Unlock(notification.Name, whoAmI) ls.Unlock(notification.Name, whoAmI)
done <- true done <- true
}() }()
@ -141,7 +143,7 @@ func Run(config *config.NotifierConfig, datastore database.Datastore, stopper *u
case <-done: case <-done:
break outer break outer
case <-time.After(refreshLockDuration): case <-time.After(refreshLockDuration):
datastore.Lock(notification.Name, whoAmI, lockDuration, true) ls.Lock(notification.Name, whoAmI, lockDuration, true)
} }
} }
} }
@ -149,10 +151,10 @@ func Run(config *config.NotifierConfig, datastore database.Datastore, stopper *u
log.Info("notifier service stopped") log.Info("notifier service stopped")
} }
func findTask(datastore database.Datastore, renotifyInterval time.Duration, whoAmI string, stopper *utils.Stopper) *database.VulnerabilityNotification { func findTask(ls locks.Service, ns notifications.Service, renotifyInterval time.Duration, whoAmI string, stopper *utils.Stopper) *services.VulnerabilityNotification {
for { for {
// Find a notification to send. // Find a notification to send.
notification, err := datastore.GetAvailableNotification(renotifyInterval) notification, err := ns.GetAvailableNotification(renotifyInterval)
if err != nil { if err != nil {
// There is no notification or an error occurred. // There is no notification or an error occurred.
if err != cerrors.ErrNotFound { if err != cerrors.ErrNotFound {
@ -168,14 +170,14 @@ func findTask(datastore database.Datastore, renotifyInterval time.Duration, whoA
} }
// Lock the notification. // Lock the notification.
if hasLock, _ := datastore.Lock(notification.Name, whoAmI, lockDuration, false); hasLock { if hasLock, _ := ls.Lock(notification.Name, whoAmI, lockDuration, false); hasLock {
log.Infof("found and locked a notification: %s", notification.Name) log.Infof("found and locked a notification: %s", notification.Name)
return &notification return &notification
} }
} }
} }
func handleTask(notification database.VulnerabilityNotification, st *utils.Stopper, maxAttempts int) (bool, bool) { func handleTask(notification services.VulnerabilityNotification, st *utils.Stopper, maxAttempts int) (bool, bool) {
// Send notification. // Send notification.
for notifierName, notifier := range notifiers { for notifierName, notifier := range notifiers {
var attempts int var attempts int

View File

@ -30,8 +30,8 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"github.com/coreos/clair/config" "github.com/coreos/clair/config"
"github.com/coreos/clair/database"
"github.com/coreos/clair/notifier" "github.com/coreos/clair/notifier"
"github.com/coreos/clair/services"
) )
const timeout = 5 * time.Second const timeout = 5 * time.Second
@ -114,7 +114,7 @@ type notificationEnvelope struct {
} }
} }
func (h *WebhookNotifier) Send(notification database.VulnerabilityNotification) error { func (h *WebhookNotifier) Send(notification services.VulnerabilityNotification) error {
// Marshal notification. // Marshal notification.
jsonNotification, err := json.Marshal(notificationEnvelope{struct{ Name string }{notification.Name}}) jsonNotification, err := json.Marshal(notificationEnvelope{struct{ Name string }{notification.Name}})
if err != nil { if err != nil {

View File

@ -0,0 +1,61 @@
// Copyright 2015 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package keyvalue defines an interface for a simple keyvalue store.
package keyvalue
import (
"fmt"
"github.com/coreos/clair/config"
"github.com/coreos/clair/services"
)
type Driver func(cfg config.RegistrableComponentConfig) (Service, error)
var keyValueDrivers = make(map[string]Driver)
// Register makes a Service constructor available by the provided name.
//
// If this function is called twice with the same name or if the Constructor is
// nil, it panics.
func Register(name string, driver Driver) {
if driver == nil {
panic("keyvalue: could not register nil Driver")
}
if _, dup := keyValueDrivers[name]; dup {
panic("keyvalue: could not register duplicate Driver: " + name)
}
keyValueDrivers[name] = driver
}
// Open opens a Datastore specified by a configuration.
func Open(cfg config.RegistrableComponentConfig) (ls Service, err error) {
driver, ok := keyValueDrivers[cfg.Type]
if !ok {
err = fmt.Errorf("keyvalue: unknown Driver %q (forgotten configuration or import?)", cfg.Type)
return
}
return driver(cfg)
}
type Service interface {
services.Base
// # Key/Value
// InsertKeyValue stores or updates a simple key/value pair in the database.
InsertKeyValue(key, value string) error
// GetKeyValue retrieves a value from the database from the given key.
// It returns an empty string if there is no such key.
GetKeyValue(key string) (string, error)
}

69
services/locks/locks.go Normal file
View File

@ -0,0 +1,69 @@
// Copyright 2015 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package locks defines an interface for interacting with named locks.
package locks
import (
"fmt"
"time"
"github.com/coreos/clair/config"
"github.com/coreos/clair/services"
)
type Driver func(cfg config.RegistrableComponentConfig) (Service, error)
var lockDrivers = make(map[string]Driver)
// Register makes a Service constructor available by the provided name.
//
// If this function is called twice with the same name or if the Constructor is
// nil, it panics.
func Register(name string, driver Driver) {
if driver == nil {
panic("locks: could not register nil Driver")
}
if _, dup := lockDrivers[name]; dup {
panic("locks: could not register duplicate Driver: " + name)
}
lockDrivers[name] = driver
}
// Open opens a Datastore specified by a configuration.
func Open(cfg config.RegistrableComponentConfig) (ls Service, err error) {
driver, ok := lockDrivers[cfg.Type]
if !ok {
err = fmt.Errorf("locks: unknown Driver %q (forgotten configuration or import?)", cfg.Type)
return
}
return driver(cfg)
}
type Service interface {
services.Base
// # Lock
// Lock creates or renew a Lock in the database with the given name, owner and duration.
// After the specified duration, the Lock expires by itself if it hasn't been unlocked, and thus,
// let other users create a Lock with the same name. However, the owner can renew its Lock by
// setting renew to true. Lock should not block, it should instead returns whether the Lock has
// been successfully acquired/renewed. If it's the case, the expiration time of that Lock is
// returned as well.
Lock(name string, owner string, duration time.Duration, renew bool) (bool, time.Time)
// Unlock releases an existing Lock.
Unlock(name, owner string)
// FindLock returns the owner of a Lock specified by the name, and its experation time if it
// exists.
FindLock(name string) (string, time.Time, error)
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package database package services
import ( import (
"database/sql/driver" "database/sql/driver"
@ -22,6 +22,8 @@ import (
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
) )
// TODO(mattmoor): Clean up the dependencies of these types so that they can be decomposed into their respective service layers. This will require eliminating some of the overloading via optional fields that is done today (e.g. AddedBy "only makes sense when ..." should probably be a FeatureVersion wrapped in a new type used in the context of an image).
// ID is only meant to be used by database implementations and should never be used for anything else. // ID is only meant to be used by database implementations and should never be used for anything else.
type Model struct { type Model struct {
ID int ID int

View File

@ -0,0 +1,79 @@
// Copyright 2015 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package notifications defines an interface for interacting with notification state.
package notifications
import (
"fmt"
"time"
"github.com/coreos/clair/config"
"github.com/coreos/clair/services"
)
type Driver func(cfg config.RegistrableComponentConfig) (Service, error)
var notificationDrivers = make(map[string]Driver)
// Register makes a Service constructor available by the provided name.
//
// If this function is called twice with the same name or if the Constructor is
// nil, it panics.
func Register(name string, driver Driver) {
if driver == nil {
panic("notifications: could not register nil Driver")
}
if _, dup := notificationDrivers[name]; dup {
panic("notifications: could not register duplicate Driver: " + name)
}
notificationDrivers[name] = driver
}
// Open opens a Datastore specified by a configuration.
func Open(cfg config.RegistrableComponentConfig) (ls Service, err error) {
driver, ok := notificationDrivers[cfg.Type]
if !ok {
err = fmt.Errorf("notifications: unknown Driver %q (forgotten configuration or import?)", cfg.Type)
return
}
return driver(cfg)
}
type Service interface {
services.Base
// # Notification
// GetAvailableNotification returns the Name, Created, Notified and Deleted fields of a
// Notification that should be handled. The renotify interval defines how much time after being
// marked as Notified by SetNotificationNotified, a Notification that hasn't been deleted should
// be returned again by this function. A Notification for which there is a valid Lock with the
// same Name should not be returned.
GetAvailableNotification(renotifyInterval time.Duration) (services.VulnerabilityNotification, error)
// GetNotification returns a Notification, including its OldVulnerability and NewVulnerability
// fields. On these Vulnerabilities, LayersIntroducingVulnerability should be filled with
// every Layer that introduces the Vulnerability (i.e. adds at least one affected FeatureVersion).
// The Limit and page parameters are used to paginate LayersIntroducingVulnerability. The first
// given page should be VulnerabilityNotificationFirstPage. The function will then return the next
// availage page. If there is no more page, NoVulnerabilityNotificationPage has to be returned.
GetNotification(name string, limit int, page services.VulnerabilityNotificationPageNumber) (services.VulnerabilityNotification, services.VulnerabilityNotificationPageNumber, error)
// SetNotificationNotified marks a Notification as notified and thus, makes it unavailable for
// GetAvailableNotification, until the renotify duration is elapsed.
SetNotificationNotified(name string) error
// DeleteNotification marks a Notification as deleted, and thus, makes it unavailable for
// GetAvailableNotification.
DeleteNotification(name string) error
}

39
services/services.go Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2015 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package services defines a set of service interfaces needed by Clair internally.
package services
import (
"errors"
)
var (
// ErrBackendException is an error that occurs when the service backend does
// not work properly (ie. unreachable).
ErrBackendException = errors.New("services: an error occured when querying the backend")
// ErrInconsistent is an error that occurs when a service consistency check
// fails (ie. when an entity which is supposed to be unique is detected twice)
ErrInconsistent = errors.New("services: inconsistent state")
)
type Base interface {
// # Miscellaneous
// Ping returns the health status of the service.
Ping() bool
// Close closes the connection to the service and free any allocated resources.
Close()
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package database package vulnerabilities
// DebianReleasesMapping translates Debian code names and class names to version numbers // DebianReleasesMapping translates Debian code names and class names to version numbers
var DebianReleasesMapping = map[string]string{ var DebianReleasesMapping = map[string]string{

View File

@ -0,0 +1,113 @@
// Copyright 2015 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package vulnerabilities defines an interface for listing, reading and writing vulnerability information
package vulnerabilities
import (
"fmt"
"github.com/coreos/clair/config"
"github.com/coreos/clair/services"
)
type Driver func(cfg config.RegistrableComponentConfig) (Service, error)
var vulnzDrivers = make(map[string]Driver)
// Register makes a Vulnerability constructor available by the provided name.
//
// If this function is called twice with the same name or if the Constructor is
// nil, it panics.
func Register(name string, driver Driver) {
if driver == nil {
panic("vulnerabilities: could not register nil Driver")
}
if _, dup := vulnzDrivers[name]; dup {
panic("vulnerabilities: could not register duplicate Driver: " + name)
}
vulnzDrivers[name] = driver
}
// Open opens a Datastore specified by a configuration.
func Open(cfg config.RegistrableComponentConfig) (ls Service, err error) {
driver, ok := vulnzDrivers[cfg.Type]
if !ok {
err = fmt.Errorf("vulnerabilities: unknown Driver %q (forgotten configuration or import?)", cfg.Type)
return
}
return driver(cfg)
}
type Service interface {
services.Base
// # Namespace
// ListNamespaces returns the entire list of known Namespaces.
ListNamespaces() ([]services.Namespace, error)
// # Vulnerability
// ListVulnerabilities returns the list of vulnerabilies of a certain Namespace.
// The Limit and page parameters are used to paginate the return list.
// The first given page should be 0. The function will then return the next available page.
// If there is no more page, -1 has to be returned.
ListVulnerabilities(namespaceName string, limit int, page int) ([]services.Vulnerability, int, error)
// InsertVulnerabilities stores the given Vulnerabilities in the database, updating them if
// necessary. A vulnerability is uniquely identified by its Namespace and its Name.
// The FixedIn field may only contain a partial list of Features that are affected by the
// Vulnerability, along with the version in which the vulnerability is fixed. It is the
// responsibility of the implementation to update the list properly. A version equals to
// types.MinVersion means that the given Feature is not being affected by the Vulnerability at
// all and thus, should be removed from the list. It is important that Features should be unique
// in the FixedIn list. For example, it doesn't make sense to have two `openssl` Feature listed as
// a Vulnerability can only be fixed in one Version. This is true because Vulnerabilities and
// Features are Namespaced (i.e. specific to one operating system).
// Each vulnerability insertion or update has to create a Notification that will contain the
// old and the updated Vulnerability, unless createNotification equals to true.
InsertVulnerabilities(vulnerabilities []services.Vulnerability, createNotification bool) error
// FindVulnerability retrieves a Vulnerability from the database, including the FixedIn list.
FindVulnerability(namespaceName, name string) (services.Vulnerability, error)
// DeleteVulnerability removes a Vulnerability from the database.
// It has to create a Notification that will contain the old Vulnerability.
DeleteVulnerability(namespaceName, name string) error
// InsertVulnerabilityFixes adds new FixedIn Feature or update the Versions of existing ones to
// the specified Vulnerability in the database.
// It has has to create a Notification that will contain the old and the updated Vulnerability.
InsertVulnerabilityFixes(vulnerabilityNamespace, vulnerabilityName string, fixes []services.FeatureVersion) error
// DeleteVulnerabilityFix removes a FixedIn Feature from the specified Vulnerability in the
// database. It can be used to store the fact that a Vulnerability no longer affects the given
// Feature in any Version.
// It has has to create a Notification that will contain the old and the updated Vulnerability.
DeleteVulnerabilityFix(vulnerabilityNamespace, vulnerabilityName, featureName string) error
// # Layer
// InsertLayer stores a Layer in the database.
// A Layer is uniquely identified by its Name. The Name and EngineVersion fields are mandatory.
// If a Parent is specified, it is expected that it has been retrieved using FindLayer.
// If a Layer that already exists is inserted and the EngineVersion of the given Layer is higher
// than the stored one, the stored Layer should be updated.
// The function has to be idempotent, inserting a layer that already exists shouln'd return an
// error.
InsertLayer(services.Layer) error
// FindLayer retrieves a Layer from the database.
// withFeatures specifies whether the Features field should be filled. When withVulnerabilities is
// true, the Features field should be filled and their AffectedBy fields should contain every
// vulnerabilities that affect them.
FindLayer(name string, withFeatures, withVulnerabilities bool) (services.Layer, error)
// DeleteLayer deletes a Layer from the database and every layers that are based on it,
// recursively.
DeleteLayer(name string) error
}

View File

@ -14,14 +14,17 @@
package updater package updater
import "github.com/coreos/clair/database" import (
"github.com/coreos/clair/services"
"github.com/coreos/clair/services/keyvalue"
)
var fetchers = make(map[string]Fetcher) var fetchers = make(map[string]Fetcher)
// Fetcher represents anything that can fetch vulnerabilities. // Fetcher represents anything that can fetch vulnerabilities.
type Fetcher interface { type Fetcher interface {
// FetchUpdate gets vulnerability updates. // FetchUpdate gets vulnerability updates.
FetchUpdate(database.Datastore) (FetcherResponse, error) FetchUpdate(keyvalue.Service) (FetcherResponse, error)
// Clean deletes any allocated resources. // Clean deletes any allocated resources.
// It is invoked when Clair stops. // It is invoked when Clair stops.
@ -33,7 +36,7 @@ type FetcherResponse struct {
FlagName string FlagName string
FlagValue string FlagValue string
Notes []string Notes []string
Vulnerabilities []database.Vulnerability Vulnerabilities []services.Vulnerability
} }
// RegisterFetcher makes a Fetcher available by the provided name. // RegisterFetcher makes a Fetcher available by the provided name.

View File

@ -23,7 +23,9 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/services/keyvalue"
"github.com/coreos/clair/services/vulnerabilities"
"github.com/coreos/clair/updater" "github.com/coreos/clair/updater"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
@ -60,7 +62,7 @@ func init() {
} }
// FetchUpdate fetches vulnerability updates from the Debian Security Tracker. // FetchUpdate fetches vulnerability updates from the Debian Security Tracker.
func (fetcher *DebianFetcher) FetchUpdate(datastore database.Datastore) (resp updater.FetcherResponse, err error) { func (fetcher *DebianFetcher) FetchUpdate(kvstore keyvalue.Service) (resp updater.FetcherResponse, err error) {
log.Info("fetching Debian vulnerabilities") log.Info("fetching Debian vulnerabilities")
// Download JSON. // Download JSON.
@ -71,7 +73,7 @@ func (fetcher *DebianFetcher) FetchUpdate(datastore database.Datastore) (resp up
} }
// Get the SHA-1 of the latest update's JSON data // Get the SHA-1 of the latest update's JSON data
latestHash, err := datastore.GetKeyValue(updaterFlag) latestHash, err := kvstore.GetKeyValue(updaterFlag)
if err != nil { if err != nil {
return resp, err return resp, err
} }
@ -130,15 +132,15 @@ func buildResponse(jsonReader io.Reader, latestKnownHash string) (resp updater.F
return resp, nil return resp, nil
} }
func parseDebianJSON(data *jsonData) (vulnerabilities []database.Vulnerability, unknownReleases map[string]struct{}) { func parseDebianJSON(data *jsonData) (vulnz []services.Vulnerability, unknownReleases map[string]struct{}) {
mvulnerabilities := make(map[string]*database.Vulnerability) mvulnerabilities := make(map[string]*services.Vulnerability)
unknownReleases = make(map[string]struct{}) unknownReleases = make(map[string]struct{})
for pkgName, pkgNode := range *data { for pkgName, pkgNode := range *data {
for vulnName, vulnNode := range pkgNode { for vulnName, vulnNode := range pkgNode {
for releaseName, releaseNode := range vulnNode.Releases { for releaseName, releaseNode := range vulnNode.Releases {
// Attempt to detect the release number. // Attempt to detect the release number.
if _, isReleaseKnown := database.DebianReleasesMapping[releaseName]; !isReleaseKnown { if _, isReleaseKnown := vulnerabilities.DebianReleasesMapping[releaseName]; !isReleaseKnown {
unknownReleases[releaseName] = struct{}{} unknownReleases[releaseName] = struct{}{}
continue continue
} }
@ -151,7 +153,7 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.Vulnerability,
// Get or create the vulnerability. // Get or create the vulnerability.
vulnerability, vulnerabilityAlreadyExists := mvulnerabilities[vulnName] vulnerability, vulnerabilityAlreadyExists := mvulnerabilities[vulnName]
if !vulnerabilityAlreadyExists { if !vulnerabilityAlreadyExists {
vulnerability = &database.Vulnerability{ vulnerability = &services.Vulnerability{
Name: vulnName, Name: vulnName,
Link: strings.Join([]string{cveURLPrefix, "/", vulnName}, ""), Link: strings.Join([]string{cveURLPrefix, "/", vulnName}, ""),
Severity: types.Unknown, Severity: types.Unknown,
@ -188,11 +190,11 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.Vulnerability,
} }
// Create and add the feature version. // Create and add the feature version.
pkg := database.FeatureVersion{ pkg := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Name: pkgName, Name: pkgName,
Namespace: database.Namespace{ Namespace: services.Namespace{
Name: "debian:" + database.DebianReleasesMapping[releaseName], Name: "debian:" + vulnerabilities.DebianReleasesMapping[releaseName],
}, },
}, },
Version: version, Version: version,
@ -207,7 +209,7 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.Vulnerability,
// Convert the vulnerabilities map to a slice // Convert the vulnerabilities map to a slice
for _, v := range mvulnerabilities { for _, v := range mvulnerabilities {
vulnerabilities = append(vulnerabilities, *v) vulnz = append(vulnz, *v)
} }
return return

View File

@ -20,7 +20,7 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -38,17 +38,17 @@ func TestDebianParser(t *testing.T) {
assert.Equal(t, types.Low, vulnerability.Severity) assert.Equal(t, types.Low, vulnerability.Severity)
assert.Equal(t, "This vulnerability is not very dangerous.", vulnerability.Description) assert.Equal(t, "This vulnerability is not very dangerous.", vulnerability.Description)
expectedFeatureVersions := []database.FeatureVersion{ expectedFeatureVersions := []services.FeatureVersion{
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "debian:8"}, Namespace: services.Namespace{Name: "debian:8"},
Name: "aptdaemon", Name: "aptdaemon",
}, },
Version: types.MaxVersion, Version: types.MaxVersion,
}, },
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "debian:unstable"}, Namespace: services.Namespace{Name: "debian:unstable"},
Name: "aptdaemon", Name: "aptdaemon",
}, },
@ -64,24 +64,24 @@ func TestDebianParser(t *testing.T) {
assert.Equal(t, types.High, vulnerability.Severity) assert.Equal(t, types.High, vulnerability.Severity)
assert.Equal(t, "But this one is very dangerous.", vulnerability.Description) assert.Equal(t, "But this one is very dangerous.", vulnerability.Description)
expectedFeatureVersions := []database.FeatureVersion{ expectedFeatureVersions := []services.FeatureVersion{
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "debian:8"}, Namespace: services.Namespace{Name: "debian:8"},
Name: "aptdaemon", Name: "aptdaemon",
}, },
Version: types.NewVersionUnsafe("0.7.0"), Version: types.NewVersionUnsafe("0.7.0"),
}, },
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "debian:unstable"}, Namespace: services.Namespace{Name: "debian:unstable"},
Name: "aptdaemon", Name: "aptdaemon",
}, },
Version: types.NewVersionUnsafe("0.7.0"), Version: types.NewVersionUnsafe("0.7.0"),
}, },
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "debian:8"}, Namespace: services.Namespace{Name: "debian:8"},
Name: "asterisk", Name: "asterisk",
}, },
Version: types.NewVersionUnsafe("0.5.56"), Version: types.NewVersionUnsafe("0.5.56"),
@ -96,10 +96,10 @@ func TestDebianParser(t *testing.T) {
assert.Equal(t, types.Negligible, vulnerability.Severity) assert.Equal(t, types.Negligible, vulnerability.Severity)
assert.Equal(t, "Un-affected packages.", vulnerability.Description) assert.Equal(t, "Un-affected packages.", vulnerability.Description)
expectedFeatureVersions := []database.FeatureVersion{ expectedFeatureVersions := []services.FeatureVersion{
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "debian:8"}, Namespace: services.Namespace{Name: "debian:8"},
Name: "asterisk", Name: "asterisk",
}, },
Version: types.MinVersion, Version: types.MinVersion,

View File

@ -23,7 +23,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/services/keyvalue"
"github.com/coreos/clair/updater" "github.com/coreos/clair/updater"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
@ -88,11 +89,11 @@ func init() {
} }
// FetchUpdate gets vulnerability updates from the Red Hat OVAL definitions. // FetchUpdate gets vulnerability updates from the Red Hat OVAL definitions.
func (f *RHELFetcher) FetchUpdate(datastore database.Datastore) (resp updater.FetcherResponse, err error) { func (f *RHELFetcher) FetchUpdate(kvstore keyvalue.Service) (resp updater.FetcherResponse, err error) {
log.Info("fetching Red Hat vulnerabilities") log.Info("fetching Red Hat vulnerabilities")
// Get the first RHSA we have to manage. // Get the first RHSA we have to manage.
flagValue, err := datastore.GetKeyValue(updaterFlag) flagValue, err := kvstore.GetKeyValue(updaterFlag)
if err != nil { if err != nil {
return resp, err return resp, err
} }
@ -153,7 +154,7 @@ func (f *RHELFetcher) FetchUpdate(datastore database.Datastore) (resp updater.Fe
return resp, nil return resp, nil
} }
func parseRHSA(ovalReader io.Reader) (vulnerabilities []database.Vulnerability, err error) { func parseRHSA(ovalReader io.Reader) (vulnerabilities []services.Vulnerability, err error) {
// Decode the XML. // Decode the XML.
var ov oval var ov oval
err = xml.NewDecoder(ovalReader).Decode(&ov) err = xml.NewDecoder(ovalReader).Decode(&ov)
@ -168,7 +169,7 @@ func parseRHSA(ovalReader io.Reader) (vulnerabilities []database.Vulnerability,
for _, definition := range ov.Definitions { for _, definition := range ov.Definitions {
pkgs := toFeatureVersions(definition.Criteria) pkgs := toFeatureVersions(definition.Criteria)
if len(pkgs) > 0 { if len(pkgs) > 0 {
vulnerability := database.Vulnerability{ vulnerability := services.Vulnerability{
Name: name(definition), Name: name(definition),
Link: link(definition), Link: link(definition),
Severity: priority(definition), Severity: priority(definition),
@ -259,15 +260,15 @@ func getPossibilities(node criteria) [][]criterion {
return possibilities return possibilities
} }
func toFeatureVersions(criteria criteria) []database.FeatureVersion { func toFeatureVersions(criteria criteria) []services.FeatureVersion {
// There are duplicates in Red Hat .xml files. // There are duplicates in Red Hat .xml files.
// This map is for deduplication. // This map is for deduplication.
featureVersionParameters := make(map[string]database.FeatureVersion) featureVersionParameters := make(map[string]services.FeatureVersion)
possibilities := getPossibilities(criteria) possibilities := getPossibilities(criteria)
for _, criterions := range possibilities { for _, criterions := range possibilities {
var ( var (
featureVersion database.FeatureVersion featureVersion services.FeatureVersion
osVersion int osVersion int
err error err error
) )
@ -304,7 +305,7 @@ func toFeatureVersions(criteria criteria) []database.FeatureVersion {
} }
// Convert the map to slice. // Convert the map to slice.
var featureVersionParametersArray []database.FeatureVersion var featureVersionParametersArray []services.FeatureVersion
for _, fv := range featureVersionParameters { for _, fv := range featureVersionParameters {
featureVersionParametersArray = append(featureVersionParametersArray, fv) featureVersionParametersArray = append(featureVersionParametersArray, fv)
} }

View File

@ -20,7 +20,7 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -38,24 +38,24 @@ func TestRHELParser(t *testing.T) {
assert.Equal(t, types.Medium, vulnerabilities[0].Severity) assert.Equal(t, types.Medium, vulnerabilities[0].Severity)
assert.Equal(t, `Xerces-C is a validating XML parser written in a portable subset of C++. A flaw was found in the way the Xerces-C XML parser processed certain XML documents. A remote attacker could provide specially crafted XML input that, when parsed by an application using Xerces-C, would cause that application to crash.`, vulnerabilities[0].Description) assert.Equal(t, `Xerces-C is a validating XML parser written in a portable subset of C++. A flaw was found in the way the Xerces-C XML parser processed certain XML documents. A remote attacker could provide specially crafted XML input that, when parsed by an application using Xerces-C, would cause that application to crash.`, vulnerabilities[0].Description)
expectedFeatureVersions := []database.FeatureVersion{ expectedFeatureVersions := []services.FeatureVersion{
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "centos:7"}, Namespace: services.Namespace{Name: "centos:7"},
Name: "xerces-c", Name: "xerces-c",
}, },
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"), Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
}, },
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "centos:7"}, Namespace: services.Namespace{Name: "centos:7"},
Name: "xerces-c-devel", Name: "xerces-c-devel",
}, },
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"), Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
}, },
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "centos:7"}, Namespace: services.Namespace{Name: "centos:7"},
Name: "xerces-c-doc", Name: "xerces-c-doc",
}, },
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"), Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
@ -76,17 +76,17 @@ func TestRHELParser(t *testing.T) {
assert.Equal(t, types.Critical, vulnerabilities[0].Severity) assert.Equal(t, types.Critical, vulnerabilities[0].Severity)
assert.Equal(t, `Mozilla Firefox is an open source web browser. XULRunner provides the XUL Runtime environment for Mozilla Firefox. Several flaws were found in the processing of malformed web content. A web page containing malicious content could cause Firefox to crash or, potentially, execute arbitrary code with the privileges of the user running Firefox.`, vulnerabilities[0].Description) assert.Equal(t, `Mozilla Firefox is an open source web browser. XULRunner provides the XUL Runtime environment for Mozilla Firefox. Several flaws were found in the processing of malformed web content. A web page containing malicious content could cause Firefox to crash or, potentially, execute arbitrary code with the privileges of the user running Firefox.`, vulnerabilities[0].Description)
expectedFeatureVersions := []database.FeatureVersion{ expectedFeatureVersions := []services.FeatureVersion{
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "centos:6"}, Namespace: services.Namespace{Name: "centos:6"},
Name: "firefox", Name: "firefox",
}, },
Version: types.NewVersionUnsafe("38.1.0-1.el6_6"), Version: types.NewVersionUnsafe("38.1.0-1.el6_6"),
}, },
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "centos:7"}, Namespace: services.Namespace{Name: "centos:7"},
Name: "firefox", Name: "firefox",
}, },
Version: types.NewVersionUnsafe("38.1.0-1.el7_1"), Version: types.NewVersionUnsafe("38.1.0-1.el7_1"),

View File

@ -26,7 +26,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/services/keyvalue"
"github.com/coreos/clair/services/vulnerabilities"
"github.com/coreos/clair/updater" "github.com/coreos/clair/updater"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
@ -89,7 +91,7 @@ func init() {
} }
// FetchUpdate gets vulnerability updates from the Ubuntu CVE Tracker. // FetchUpdate gets vulnerability updates from the Ubuntu CVE Tracker.
func (fetcher *UbuntuFetcher) FetchUpdate(datastore database.Datastore) (resp updater.FetcherResponse, err error) { func (fetcher *UbuntuFetcher) FetchUpdate(kvstore keyvalue.Service) (resp updater.FetcherResponse, err error) {
log.Info("fetching Ubuntu vulnerabilities") log.Info("fetching Ubuntu vulnerabilities")
// Check to see if the repository does not already exist. // Check to see if the repository does not already exist.
@ -123,7 +125,7 @@ func (fetcher *UbuntuFetcher) FetchUpdate(datastore database.Datastore) (resp up
} }
// Get the latest revision number we successfully applied in the database. // Get the latest revision number we successfully applied in the database.
dbRevisionNumber, err := datastore.GetKeyValue("ubuntuUpdater") dbRevisionNumber, err := kvstore.GetKeyValue("ubuntuUpdater")
if err != nil { if err != nil {
return resp, err return resp, err
} }
@ -281,7 +283,7 @@ func getRevisionNumber(pathToRepo string) (int, error) {
return revno, nil return revno, nil
} }
func parseUbuntuCVE(fileContent io.Reader) (vulnerability database.Vulnerability, unknownReleases map[string]struct{}, err error) { func parseUbuntuCVE(fileContent io.Reader) (vulnerability services.Vulnerability, unknownReleases map[string]struct{}, err error) {
unknownReleases = make(map[string]struct{}) unknownReleases = make(map[string]struct{})
readingDescription := false readingDescription := false
scanner := bufio.NewScanner(fileContent) scanner := bufio.NewScanner(fileContent)
@ -350,7 +352,7 @@ func parseUbuntuCVE(fileContent io.Reader) (vulnerability database.Vulnerability
if _, isReleaseIgnored := ubuntuIgnoredReleases[md["release"]]; isReleaseIgnored { if _, isReleaseIgnored := ubuntuIgnoredReleases[md["release"]]; isReleaseIgnored {
continue continue
} }
if _, isReleaseKnown := database.UbuntuReleasesMapping[md["release"]]; !isReleaseKnown { if _, isReleaseKnown := vulnerabilities.UbuntuReleasesMapping[md["release"]]; !isReleaseKnown {
unknownReleases[md["release"]] = struct{}{} unknownReleases[md["release"]] = struct{}{}
continue continue
} }
@ -374,9 +376,9 @@ func parseUbuntuCVE(fileContent io.Reader) (vulnerability database.Vulnerability
} }
// Create and add the new package. // Create and add the new package.
featureVersion := database.FeatureVersion{ featureVersion := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "ubuntu:" + database.UbuntuReleasesMapping[md["release"]]}, Namespace: services.Namespace{Name: "ubuntu:" + vulnerabilities.UbuntuReleasesMapping[md["release"]]},
Name: md["package"], Name: md["package"],
}, },
Version: version, Version: version,

View File

@ -20,7 +20,7 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -42,24 +42,24 @@ func TestUbuntuParser(t *testing.T) {
_, hasUnkownRelease := unknownReleases["unknown"] _, hasUnkownRelease := unknownReleases["unknown"]
assert.True(t, hasUnkownRelease) assert.True(t, hasUnkownRelease)
expectedFeatureVersions := []database.FeatureVersion{ expectedFeatureVersions := []services.FeatureVersion{
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "ubuntu:14.04"}, Namespace: services.Namespace{Name: "ubuntu:14.04"},
Name: "libmspack", Name: "libmspack",
}, },
Version: types.MaxVersion, Version: types.MaxVersion,
}, },
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "ubuntu:15.04"}, Namespace: services.Namespace{Name: "ubuntu:15.04"},
Name: "libmspack", Name: "libmspack",
}, },
Version: types.NewVersionUnsafe("0.4-3"), Version: types.NewVersionUnsafe("0.4-3"),
}, },
{ {
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "ubuntu:15.10"}, Namespace: services.Namespace{Name: "ubuntu:15.10"},
Name: "libmspack-anotherpkg", Name: "libmspack-anotherpkg",
}, },
Version: types.NewVersionUnsafe("0.1"), Version: types.NewVersionUnsafe("0.1"),

View File

@ -17,22 +17,23 @@ package updater
import ( import (
"sync" "sync"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/services/vulnerabilities"
) )
var metadataFetchers = make(map[string]MetadataFetcher) var metadataFetchers = make(map[string]MetadataFetcher)
type VulnerabilityWithLock struct { type VulnerabilityWithLock struct {
*database.Vulnerability *services.Vulnerability
Lock sync.Mutex Lock sync.Mutex
} }
// MetadataFetcher // MetadataFetcher
type MetadataFetcher interface { type MetadataFetcher interface {
// Load runs right before the Updater calls AddMetadata for each vulnerabilities. // Load runs right before the Updater calls AddMetadata for each vulnerabilities.
Load(database.Datastore) error Load(vulnerabilities.Service) error
// AddMetadata adds metadata to the given database.Vulnerability. // AddMetadata adds metadata to the given services.Vulnerability.
// It is expected that the fetcher uses .Lock.Lock() when manipulating the Metadata map. // It is expected that the fetcher uses .Lock.Lock() when manipulating the Metadata map.
AddMetadata(*VulnerabilityWithLock) error AddMetadata(*VulnerabilityWithLock) error

View File

@ -15,7 +15,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/coreos/clair/database" "github.com/coreos/clair/services/vulnerabilities"
"github.com/coreos/clair/updater" "github.com/coreos/clair/updater"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
@ -53,7 +53,7 @@ func init() {
updater.RegisterMetadataFetcher("NVD", &NVDMetadataFetcher{}) updater.RegisterMetadataFetcher("NVD", &NVDMetadataFetcher{})
} }
func (fetcher *NVDMetadataFetcher) Load(datastore database.Datastore) error { func (fetcher *NVDMetadataFetcher) Load(vulnstore vulnerabilities.Service) error {
fetcher.lock.Lock() fetcher.lock.Lock()
defer fetcher.lock.Unlock() defer fetcher.lock.Unlock()

View File

@ -23,7 +23,10 @@ import (
"time" "time"
"github.com/coreos/clair/config" "github.com/coreos/clair/config"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/services/keyvalue"
"github.com/coreos/clair/services/locks"
"github.com/coreos/clair/services/vulnerabilities"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
"github.com/pborman/uuid" "github.com/pborman/uuid"
@ -65,7 +68,7 @@ func init() {
} }
// Run updates the vulnerability database at regular intervals. // Run updates the vulnerability database at regular intervals.
func Run(config *config.UpdaterConfig, datastore database.Datastore, st *utils.Stopper) { func Run(config *config.UpdaterConfig, locksvc locks.Service, kvstore keyvalue.Service, vulnstore vulnerabilities.Service, st *utils.Stopper) {
defer st.End() defer st.End()
// Do not run the updater if there is no config or if the interval is 0. // Do not run the updater if there is no config or if the interval is 0.
@ -83,7 +86,7 @@ func Run(config *config.UpdaterConfig, datastore database.Datastore, st *utils.S
// Determine if this is the first update and define the next update time. // Determine if this is the first update and define the next update time.
// The next update time is (last update time + interval) or now if this is the first update. // The next update time is (last update time + interval) or now if this is the first update.
nextUpdate := time.Now().UTC() nextUpdate := time.Now().UTC()
lastUpdate, firstUpdate, err := getLastUpdate(datastore) lastUpdate, firstUpdate, err := getLastUpdate(kvstore)
if err != nil { if err != nil {
log.Errorf("an error occured while getting the last update time") log.Errorf("an error occured while getting the last update time")
nextUpdate = nextUpdate.Add(config.Interval) nextUpdate = nextUpdate.Add(config.Interval)
@ -95,12 +98,12 @@ func Run(config *config.UpdaterConfig, datastore database.Datastore, st *utils.S
if nextUpdate.Before(time.Now().UTC()) { if nextUpdate.Before(time.Now().UTC()) {
// Attempt to get a lock on the the update. // Attempt to get a lock on the the update.
log.Debug("attempting to obtain update lock") log.Debug("attempting to obtain update lock")
hasLock, hasLockUntil := datastore.Lock(lockName, whoAmI, lockDuration, false) hasLock, hasLockUntil := locksvc.Lock(lockName, whoAmI, lockDuration, false)
if hasLock { if hasLock {
// Launch update in a new go routine. // Launch update in a new go routine.
doneC := make(chan bool, 1) doneC := make(chan bool, 1)
go func() { go func() {
Update(datastore, firstUpdate) Update(kvstore, vulnstore, firstUpdate)
doneC <- true doneC <- true
}() }()
@ -110,21 +113,21 @@ func Run(config *config.UpdaterConfig, datastore database.Datastore, st *utils.S
done = true done = true
case <-time.After(refreshLockDuration): case <-time.After(refreshLockDuration):
// Refresh the lock until the update is done. // Refresh the lock until the update is done.
datastore.Lock(lockName, whoAmI, lockDuration, true) locksvc.Lock(lockName, whoAmI, lockDuration, true)
case <-st.Chan(): case <-st.Chan():
stop = true stop = true
} }
} }
// Unlock the update. // Unlock the update.
datastore.Unlock(lockName, whoAmI) locksvc.Unlock(lockName, whoAmI)
if stop { if stop {
break break
} }
continue continue
} else { } else {
lockOwner, lockExpiration, err := datastore.FindLock(lockName) lockOwner, lockExpiration, err := locksvc.FindLock(lockName)
if err != nil { if err != nil {
log.Debug("update lock is already taken") log.Debug("update lock is already taken")
nextUpdate = hasLockUntil nextUpdate = hasLockUntil
@ -159,17 +162,17 @@ func Run(config *config.UpdaterConfig, datastore database.Datastore, st *utils.S
// Update fetches all the vulnerabilities from the registered fetchers, upserts // Update fetches all the vulnerabilities from the registered fetchers, upserts
// them into the database and then sends notifications. // them into the database and then sends notifications.
func Update(datastore database.Datastore, firstUpdate bool) { func Update(kvstore keyvalue.Service, vulnstore vulnerabilities.Service, firstUpdate bool) {
defer setUpdaterDuration(time.Now()) defer setUpdaterDuration(time.Now())
log.Info("updating vulnerabilities") log.Info("updating vulnerabilities")
// Fetch updates. // Fetch updates.
status, vulnerabilities, flags, notes := fetch(datastore) status, vulnerabilities, flags, notes := fetch(kvstore, vulnstore)
// Insert vulnerabilities. // Insert vulnerabilities.
log.Tracef("inserting %d vulnerabilities for update", len(vulnerabilities)) log.Tracef("inserting %d vulnerabilities for update", len(vulnerabilities))
err := datastore.InsertVulnerabilities(vulnerabilities, !firstUpdate) err := vulnstore.InsertVulnerabilities(vulnerabilities, !firstUpdate)
if err != nil { if err != nil {
promUpdaterErrorsTotal.Inc() promUpdaterErrorsTotal.Inc()
log.Errorf("an error occured when inserting vulnerabilities for update: %s", err) log.Errorf("an error occured when inserting vulnerabilities for update: %s", err)
@ -179,7 +182,7 @@ func Update(datastore database.Datastore, firstUpdate bool) {
// Update flags. // Update flags.
for flagName, flagValue := range flags { for flagName, flagValue := range flags {
datastore.InsertKeyValue(flagName, flagValue) kvstore.InsertKeyValue(flagName, flagValue)
} }
// Log notes. // Log notes.
@ -190,7 +193,7 @@ func Update(datastore database.Datastore, firstUpdate bool) {
// Update last successful update if every fetchers worked properly. // Update last successful update if every fetchers worked properly.
if status { if status {
datastore.InsertKeyValue(flagName, strconv.FormatInt(time.Now().UTC().Unix(), 10)) kvstore.InsertKeyValue(flagName, strconv.FormatInt(time.Now().UTC().Unix(), 10))
} }
log.Info("update finished") log.Info("update finished")
@ -201,8 +204,8 @@ func setUpdaterDuration(start time.Time) {
} }
// fetch get data from the registered fetchers, in parallel. // fetch get data from the registered fetchers, in parallel.
func fetch(datastore database.Datastore) (bool, []database.Vulnerability, map[string]string, []string) { func fetch(kvstore keyvalue.Service, vulnstore vulnerabilities.Service) (bool, []services.Vulnerability, map[string]string, []string) {
var vulnerabilities []database.Vulnerability var vulnerabilities []services.Vulnerability
var notes []string var notes []string
status := true status := true
flags := make(map[string]string) flags := make(map[string]string)
@ -212,7 +215,7 @@ func fetch(datastore database.Datastore) (bool, []database.Vulnerability, map[st
var responseC = make(chan *FetcherResponse, 0) var responseC = make(chan *FetcherResponse, 0)
for n, f := range fetchers { for n, f := range fetchers {
go func(name string, fetcher Fetcher) { go func(name string, fetcher Fetcher) {
response, err := fetcher.FetchUpdate(datastore) response, err := fetcher.FetchUpdate(kvstore)
if err != nil { if err != nil {
promUpdaterErrorsTotal.Inc() promUpdaterErrorsTotal.Inc()
log.Errorf("an error occured when fetching update '%s': %s.", name, err) log.Errorf("an error occured when fetching update '%s': %s.", name, err)
@ -238,11 +241,11 @@ func fetch(datastore database.Datastore) (bool, []database.Vulnerability, map[st
} }
close(responseC) close(responseC)
return status, addMetadata(datastore, vulnerabilities), flags, notes return status, addMetadata(vulnstore, vulnerabilities), flags, notes
} }
// Add metadata to the specified vulnerabilities using the registered MetadataFetchers, in parallel. // Add metadata to the specified vulnerabilities using the registered MetadataFetchers, in parallel.
func addMetadata(datastore database.Datastore, vulnerabilities []database.Vulnerability) []database.Vulnerability { func addMetadata(vulnstore vulnerabilities.Service, vulnerabilities []services.Vulnerability) []services.Vulnerability {
if len(metadataFetchers) == 0 { if len(metadataFetchers) == 0 {
return vulnerabilities return vulnerabilities
} }
@ -266,7 +269,7 @@ func addMetadata(datastore database.Datastore, vulnerabilities []database.Vulner
defer wg.Done() defer wg.Done()
// Load the metadata fetcher. // Load the metadata fetcher.
if err := metadataFetcher.Load(datastore); err != nil { if err := metadataFetcher.Load(vulnstore); err != nil {
promUpdaterErrorsTotal.Inc() promUpdaterErrorsTotal.Inc()
log.Errorf("an error occured when loading metadata fetcher '%s': %s.", name, err) log.Errorf("an error occured when loading metadata fetcher '%s': %s.", name, err)
return return
@ -286,8 +289,8 @@ func addMetadata(datastore database.Datastore, vulnerabilities []database.Vulner
return vulnerabilities return vulnerabilities
} }
func getLastUpdate(datastore database.Datastore) (time.Time, bool, error) { func getLastUpdate(kvstore keyvalue.Service) (time.Time, bool, error) {
lastUpdateTSS, err := datastore.GetKeyValue(flagName) lastUpdateTSS, err := kvstore.GetKeyValue(flagName)
if err != nil { if err != nil {
return time.Time{}, false, err return time.Time{}, false, err
} }
@ -311,12 +314,12 @@ func getLastUpdate(datastore database.Datastore) (time.Time, bool, error) {
// //
// It helps simplifying the fetchers that share the same metadata about a Vulnerability regardless // It helps simplifying the fetchers that share the same metadata about a Vulnerability regardless
// of their actual namespace (ie. same vulnerability information for every version of a distro). // of their actual namespace (ie. same vulnerability information for every version of a distro).
func doVulnerabilitiesNamespacing(vulnerabilities []database.Vulnerability) []database.Vulnerability { func doVulnerabilitiesNamespacing(vulnerabilities []services.Vulnerability) []services.Vulnerability {
vulnerabilitiesMap := make(map[string]*database.Vulnerability) vulnerabilitiesMap := make(map[string]*services.Vulnerability)
for _, v := range vulnerabilities { for _, v := range vulnerabilities {
featureVersions := v.FixedIn featureVersions := v.FixedIn
v.FixedIn = []database.FeatureVersion{} v.FixedIn = []services.FeatureVersion{}
for _, fv := range featureVersions { for _, fv := range featureVersions {
index := fv.Feature.Namespace.Name + ":" + v.Name index := fv.Feature.Namespace.Name + ":" + v.Name
@ -324,7 +327,7 @@ func doVulnerabilitiesNamespacing(vulnerabilities []database.Vulnerability) []da
if vulnerability, ok := vulnerabilitiesMap[index]; !ok { if vulnerability, ok := vulnerabilitiesMap[index]; !ok {
newVulnerability := v newVulnerability := v
newVulnerability.Namespace.Name = fv.Feature.Namespace.Name newVulnerability.Namespace.Name = fv.Feature.Namespace.Name
newVulnerability.FixedIn = []database.FeatureVersion{fv} newVulnerability.FixedIn = []services.FeatureVersion{fv}
vulnerabilitiesMap[index] = &newVulnerability vulnerabilitiesMap[index] = &newVulnerability
} else { } else {
@ -334,7 +337,7 @@ func doVulnerabilitiesNamespacing(vulnerabilities []database.Vulnerability) []da
} }
// Convert map into a slice. // Convert map into a slice.
var response []database.Vulnerability var response []services.Vulnerability
for _, vulnerability := range vulnerabilitiesMap { for _, vulnerability := range vulnerabilitiesMap {
response = append(response, *vulnerability) response = append(response, *vulnerability)
} }

View File

@ -4,42 +4,42 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestDoVulnerabilitiesNamespacing(t *testing.T) { func TestDoVulnerabilitiesNamespacing(t *testing.T) {
fv1 := database.FeatureVersion{ fv1 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "Namespace1"}, Namespace: services.Namespace{Name: "Namespace1"},
Name: "Feature1", Name: "Feature1",
}, },
Version: types.NewVersionUnsafe("0.1"), Version: types.NewVersionUnsafe("0.1"),
} }
fv2 := database.FeatureVersion{ fv2 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "Namespace2"}, Namespace: services.Namespace{Name: "Namespace2"},
Name: "Feature1", Name: "Feature1",
}, },
Version: types.NewVersionUnsafe("0.2"), Version: types.NewVersionUnsafe("0.2"),
} }
fv3 := database.FeatureVersion{ fv3 := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Namespace: database.Namespace{Name: "Namespace2"}, Namespace: services.Namespace{Name: "Namespace2"},
Name: "Feature2", Name: "Feature2",
}, },
Version: types.NewVersionUnsafe("0.3"), Version: types.NewVersionUnsafe("0.3"),
} }
vulnerability := database.Vulnerability{ vulnerability := services.Vulnerability{
Name: "DoVulnerabilityNamespacing", Name: "DoVulnerabilityNamespacing",
FixedIn: []database.FeatureVersion{fv1, fv2, fv3}, FixedIn: []services.FeatureVersion{fv1, fv2, fv3},
} }
vulnerabilities := doVulnerabilitiesNamespacing([]database.Vulnerability{vulnerability}) vulnerabilities := doVulnerabilitiesNamespacing([]services.Vulnerability{vulnerability})
for _, vulnerability := range vulnerabilities { for _, vulnerability := range vulnerabilities {
switch vulnerability.Namespace.Name { switch vulnerability.Namespace.Name {
case fv1.Feature.Namespace.Name: case fv1.Feature.Namespace.Name:

View File

@ -20,7 +20,7 @@ import (
"io" "io"
"net/http" "net/http"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/worker" "github.com/coreos/clair/worker"
@ -54,7 +54,7 @@ func WriteHTTPError(w http.ResponseWriter, httpStatus int, err error) {
switch err { switch err {
case cerrors.ErrNotFound: case cerrors.ErrNotFound:
httpStatus = http.StatusNotFound httpStatus = http.StatusNotFound
case database.ErrBackendException: case services.ErrBackendException:
httpStatus = http.StatusServiceUnavailable httpStatus = http.StatusServiceUnavailable
case worker.ErrParentUnknown, worker.ErrUnsupported, utils.ErrCouldNotExtract, utils.ErrExtractedFileTooBig: case worker.ErrParentUnknown, worker.ErrUnsupported, utils.ErrCouldNotExtract, utils.ErrExtractedFileTooBig:
httpStatus = http.StatusBadRequest httpStatus = http.StatusBadRequest

View File

@ -19,7 +19,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
"github.com/coreos/clair/worker/detectors" "github.com/coreos/clair/worker/detectors"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
@ -40,16 +40,16 @@ func init() {
} }
// Detect detects packages using var/lib/dpkg/status from the input data // Detect detects packages using var/lib/dpkg/status from the input data
func (detector *DpkgFeaturesDetector) Detect(data map[string][]byte) ([]database.FeatureVersion, error) { func (detector *DpkgFeaturesDetector) Detect(data map[string][]byte) ([]services.FeatureVersion, error) {
f, hasFile := data["var/lib/dpkg/status"] f, hasFile := data["var/lib/dpkg/status"]
if !hasFile { if !hasFile {
return []database.FeatureVersion{}, nil return []services.FeatureVersion{}, nil
} }
// Create a map to store packages and ensure their uniqueness // Create a map to store packages and ensure their uniqueness
packagesMap := make(map[string]database.FeatureVersion) packagesMap := make(map[string]services.FeatureVersion)
var pkg database.FeatureVersion var pkg services.FeatureVersion
var err error var err error
scanner := bufio.NewScanner(strings.NewReader(string(f))) scanner := bufio.NewScanner(strings.NewReader(string(f)))
for scanner.Scan() { for scanner.Scan() {
@ -100,7 +100,7 @@ func (detector *DpkgFeaturesDetector) Detect(data map[string][]byte) ([]database
} }
// Convert the map to a slice // Convert the map to a slice
packages := make([]database.FeatureVersion, 0, len(packagesMap)) packages := make([]services.FeatureVersion, 0, len(packagesMap))
for _, pkg := range packagesMap { for _, pkg := range packagesMap {
packages = append(packages, pkg) packages = append(packages, pkg)
} }

View File

@ -17,7 +17,7 @@ package dpkg
import ( import (
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
"github.com/coreos/clair/worker/detectors/feature" "github.com/coreos/clair/worker/detectors/feature"
) )
@ -25,18 +25,18 @@ import (
var dpkgPackagesTests = []feature.FeatureVersionTest{ var dpkgPackagesTests = []feature.FeatureVersionTest{
// Test an Ubuntu dpkg status file // Test an Ubuntu dpkg status file
{ {
FeatureVersions: []database.FeatureVersion{ FeatureVersions: []services.FeatureVersion{
// Two packages from this source are installed, it should only appear one time // Two packages from this source are installed, it should only appear one time
{ {
Feature: database.Feature{Name: "pam"}, Feature: services.Feature{Name: "pam"},
Version: types.NewVersionUnsafe("1.1.8-3.1ubuntu3"), Version: types.NewVersionUnsafe("1.1.8-3.1ubuntu3"),
}, },
{ {
Feature: database.Feature{Name: "makedev"}, // The source name and the package name are equals Feature: services.Feature{Name: "makedev"}, // The source name and the package name are equals
Version: types.NewVersionUnsafe("2.3.1-93ubuntu1"), // The version comes from the "Version:" line Version: types.NewVersionUnsafe("2.3.1-93ubuntu1"), // The version comes from the "Version:" line
}, },
{ {
Feature: database.Feature{Name: "gcc-5"}, Feature: services.Feature{Name: "gcc-5"},
Version: types.NewVersionUnsafe("5.1.1-12ubuntu1"), // The version comes from the "Source:" line Version: types.NewVersionUnsafe("5.1.1-12ubuntu1"), // The version comes from the "Source:" line
}, },
}, },

View File

@ -20,7 +20,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
@ -39,27 +39,27 @@ func init() {
} }
// Detect detects packages using var/lib/rpm/Packages from the input data // Detect detects packages using var/lib/rpm/Packages from the input data
func (detector *RpmFeaturesDetector) Detect(data map[string][]byte) ([]database.FeatureVersion, error) { func (detector *RpmFeaturesDetector) Detect(data map[string][]byte) ([]services.FeatureVersion, error) {
f, hasFile := data["var/lib/rpm/Packages"] f, hasFile := data["var/lib/rpm/Packages"]
if !hasFile { if !hasFile {
return []database.FeatureVersion{}, nil return []services.FeatureVersion{}, nil
} }
// Create a map to store packages and ensure their uniqueness // Create a map to store packages and ensure their uniqueness
packagesMap := make(map[string]database.FeatureVersion) packagesMap := make(map[string]services.FeatureVersion)
// Write the required "Packages" file to disk // Write the required "Packages" file to disk
tmpDir, err := ioutil.TempDir(os.TempDir(), "rpm") tmpDir, err := ioutil.TempDir(os.TempDir(), "rpm")
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
if err != nil { if err != nil {
log.Errorf("could not create temporary folder for RPM detection: %s", err) log.Errorf("could not create temporary folder for RPM detection: %s", err)
return []database.FeatureVersion{}, cerrors.ErrFilesystem return []services.FeatureVersion{}, cerrors.ErrFilesystem
} }
err = ioutil.WriteFile(tmpDir+"/Packages", f, 0700) err = ioutil.WriteFile(tmpDir+"/Packages", f, 0700)
if err != nil { if err != nil {
log.Errorf("could not create temporary file for RPM detection: %s", err) log.Errorf("could not create temporary file for RPM detection: %s", err)
return []database.FeatureVersion{}, cerrors.ErrFilesystem return []services.FeatureVersion{}, cerrors.ErrFilesystem
} }
// Query RPM // Query RPM
@ -70,7 +70,7 @@ func (detector *RpmFeaturesDetector) Detect(data map[string][]byte) ([]database.
log.Errorf("could not query RPM: %s. output: %s", err, string(out)) log.Errorf("could not query RPM: %s. output: %s", err, string(out))
// Do not bubble up because we probably won't be able to fix it, // Do not bubble up because we probably won't be able to fix it,
// the database must be corrupted // the database must be corrupted
return []database.FeatureVersion{}, nil return []services.FeatureVersion{}, nil
} }
scanner := bufio.NewScanner(strings.NewReader(string(out))) scanner := bufio.NewScanner(strings.NewReader(string(out)))
@ -95,8 +95,8 @@ func (detector *RpmFeaturesDetector) Detect(data map[string][]byte) ([]database.
} }
// Add package // Add package
pkg := database.FeatureVersion{ pkg := services.FeatureVersion{
Feature: database.Feature{ Feature: services.Feature{
Name: line[0], Name: line[0],
}, },
Version: version, Version: version,
@ -105,7 +105,7 @@ func (detector *RpmFeaturesDetector) Detect(data map[string][]byte) ([]database.
} }
// Convert the map to a slice // Convert the map to a slice
packages := make([]database.FeatureVersion, 0, len(packagesMap)) packages := make([]services.FeatureVersion, 0, len(packagesMap))
for _, pkg := range packagesMap { for _, pkg := range packagesMap {
packages = append(packages, pkg) packages = append(packages, pkg)
} }

View File

@ -17,7 +17,7 @@ package rpm
import ( import (
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
"github.com/coreos/clair/worker/detectors/feature" "github.com/coreos/clair/worker/detectors/feature"
) )
@ -26,15 +26,15 @@ var rpmPackagesTests = []feature.FeatureVersionTest{
// Test a CentOS 7 RPM database // Test a CentOS 7 RPM database
// Memo: Use the following command on a RPM-based system to shrink a database: rpm -qa --qf "%{NAME}\n" |tail -n +3| xargs rpm -e --justdb // Memo: Use the following command on a RPM-based system to shrink a database: rpm -qa --qf "%{NAME}\n" |tail -n +3| xargs rpm -e --justdb
{ {
FeatureVersions: []database.FeatureVersion{ FeatureVersions: []services.FeatureVersion{
// Two packages from this source are installed, it should only appear once // Two packages from this source are installed, it should only appear once
{ {
Feature: database.Feature{Name: "centos-release"}, Feature: services.Feature{Name: "centos-release"},
Version: types.NewVersionUnsafe("7-1.1503.el7.centos.2.8"), Version: types.NewVersionUnsafe("7-1.1503.el7.centos.2.8"),
}, },
// Two packages from this source are installed, it should only appear once // Two packages from this source are installed, it should only appear once
{ {
Feature: database.Feature{Name: "filesystem"}, Feature: services.Feature{Name: "filesystem"},
Version: types.NewVersionUnsafe("3.2-18.el7"), Version: types.NewVersionUnsafe("3.2-18.el7"),
}, },
}, },

View File

@ -20,13 +20,13 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/worker/detectors" "github.com/coreos/clair/worker/detectors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
type FeatureVersionTest struct { type FeatureVersionTest struct {
FeatureVersions []database.FeatureVersion FeatureVersions []services.FeatureVersion
Data map[string][]byte Data map[string][]byte
} }

View File

@ -18,13 +18,13 @@ import (
"fmt" "fmt"
"sync" "sync"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
) )
// The FeaturesDetector interface defines a way to detect packages from input data. // The FeaturesDetector interface defines a way to detect packages from input data.
type FeaturesDetector interface { type FeaturesDetector interface {
// Detect detects a list of FeatureVersion from the input data. // Detect detects a list of FeatureVersion from the input data.
Detect(map[string][]byte) ([]database.FeatureVersion, error) Detect(map[string][]byte) ([]services.FeatureVersion, error)
// GetRequiredFiles returns the list of files required for Detect, without // GetRequiredFiles returns the list of files required for Detect, without
// leading /. // leading /.
GetRequiredFiles() []string GetRequiredFiles() []string
@ -54,13 +54,13 @@ func RegisterFeaturesDetector(name string, f FeaturesDetector) {
} }
// DetectFeatures detects a list of FeatureVersion using every registered FeaturesDetector. // DetectFeatures detects a list of FeatureVersion using every registered FeaturesDetector.
func DetectFeatures(data map[string][]byte) ([]database.FeatureVersion, error) { func DetectFeatures(data map[string][]byte) ([]services.FeatureVersion, error) {
var packages []database.FeatureVersion var packages []services.FeatureVersion
for _, detector := range featuresDetectors { for _, detector := range featuresDetectors {
pkgs, err := detector.Detect(data) pkgs, err := detector.Detect(data)
if err != nil { if err != nil {
return []database.FeatureVersion{}, err return []services.FeatureVersion{}, err
} }
packages = append(packages, pkgs...) packages = append(packages, pkgs...)
} }

View File

@ -20,14 +20,14 @@ import (
"fmt" "fmt"
"sync" "sync"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
) )
// The NamespaceDetector interface defines a way to detect a Namespace from input data. // The NamespaceDetector interface defines a way to detect a Namespace from input data.
// A namespace is usually made of an Operating System name and its version. // A namespace is usually made of an Operating System name and its version.
type NamespaceDetector interface { type NamespaceDetector interface {
// Detect detects a Namespace and its version from input data. // Detect detects a Namespace and its version from input data.
Detect(map[string][]byte) *database.Namespace Detect(map[string][]byte) *services.Namespace
// GetRequiredFiles returns the list of files required for Detect, without // GetRequiredFiles returns the list of files required for Detect, without
// leading /. // leading /.
GetRequiredFiles() []string GetRequiredFiles() []string
@ -61,7 +61,7 @@ func RegisterNamespaceDetector(name string, f NamespaceDetector) {
} }
// DetectNamespace finds the OS of the layer by using every registered NamespaceDetector. // DetectNamespace finds the OS of the layer by using every registered NamespaceDetector.
func DetectNamespace(data map[string][]byte) *database.Namespace { func DetectNamespace(data map[string][]byte) *services.Namespace {
for _, detector := range namespaceDetectors { for _, detector := range namespaceDetectors {
if namespace := detector.Detect(data); namespace != nil { if namespace := detector.Detect(data); namespace != nil {
return namespace return namespace

View File

@ -18,7 +18,8 @@ import (
"bufio" "bufio"
"strings" "strings"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/services/vulnerabilities"
"github.com/coreos/clair/worker/detectors" "github.com/coreos/clair/worker/detectors"
) )
@ -33,7 +34,7 @@ func init() {
detectors.RegisterNamespaceDetector("apt-sources", &AptSourcesNamespaceDetector{}) detectors.RegisterNamespaceDetector("apt-sources", &AptSourcesNamespaceDetector{})
} }
func (detector *AptSourcesNamespaceDetector) Detect(data map[string][]byte) *database.Namespace { func (detector *AptSourcesNamespaceDetector) Detect(data map[string][]byte) *services.Namespace {
f, hasFile := data["etc/apt/sources.list"] f, hasFile := data["etc/apt/sources.list"]
if !hasFile { if !hasFile {
return nil return nil
@ -61,12 +62,12 @@ func (detector *AptSourcesNamespaceDetector) Detect(data map[string][]byte) *dat
} }
var found bool var found bool
version, found = database.DebianReleasesMapping[line[2]] version, found = vulnerabilities.DebianReleasesMapping[line[2]]
if found { if found {
OS = "debian" OS = "debian"
break break
} }
version, found = database.UbuntuReleasesMapping[line[2]] version, found = vulnerabilities.UbuntuReleasesMapping[line[2]]
if found { if found {
OS = "ubuntu" OS = "ubuntu"
break break
@ -75,7 +76,7 @@ func (detector *AptSourcesNamespaceDetector) Detect(data map[string][]byte) *dat
} }
if OS != "" && version != "" { if OS != "" && version != "" {
return &database.Namespace{Name: OS + ":" + version} return &services.Namespace{Name: OS + ":" + version}
} }
return nil return nil
} }

View File

@ -17,13 +17,13 @@ package aptsources
import ( import (
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/worker/detectors/namespace" "github.com/coreos/clair/worker/detectors/namespace"
) )
var aptSourcesOSTests = []namespace.NamespaceTest{ var aptSourcesOSTests = []namespace.NamespaceTest{
{ {
ExpectedNamespace: database.Namespace{Name: "debian:unstable"}, ExpectedNamespace: services.Namespace{Name: "debian:unstable"},
Data: map[string][]byte{ Data: map[string][]byte{
"etc/os-release": []byte( "etc/os-release": []byte(
`PRETTY_NAME="Debian GNU/Linux stretch/sid" `PRETTY_NAME="Debian GNU/Linux stretch/sid"

View File

@ -19,7 +19,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/worker/detectors" "github.com/coreos/clair/worker/detectors"
) )
@ -38,7 +38,7 @@ func init() {
detectors.RegisterNamespaceDetector("lsb-release", &LsbReleaseNamespaceDetector{}) detectors.RegisterNamespaceDetector("lsb-release", &LsbReleaseNamespaceDetector{})
} }
func (detector *LsbReleaseNamespaceDetector) Detect(data map[string][]byte) *database.Namespace { func (detector *LsbReleaseNamespaceDetector) Detect(data map[string][]byte) *services.Namespace {
f, hasFile := data["etc/lsb-release"] f, hasFile := data["etc/lsb-release"]
if !hasFile { if !hasFile {
return nil return nil
@ -70,7 +70,7 @@ func (detector *LsbReleaseNamespaceDetector) Detect(data map[string][]byte) *dat
} }
if OS != "" && version != "" { if OS != "" && version != "" {
return &database.Namespace{Name: OS + ":" + version} return &services.Namespace{Name: OS + ":" + version}
} }
return nil return nil
} }

View File

@ -17,13 +17,13 @@ package lsbrelease
import ( import (
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/worker/detectors/namespace" "github.com/coreos/clair/worker/detectors/namespace"
) )
var lsbReleaseOSTests = []namespace.NamespaceTest{ var lsbReleaseOSTests = []namespace.NamespaceTest{
{ {
ExpectedNamespace: database.Namespace{Name: "ubuntu:12.04"}, ExpectedNamespace: services.Namespace{Name: "ubuntu:12.04"},
Data: map[string][]byte{ Data: map[string][]byte{
"etc/lsb-release": []byte( "etc/lsb-release": []byte(
`DISTRIB_ID=Ubuntu `DISTRIB_ID=Ubuntu
@ -33,7 +33,7 @@ DISTRIB_DESCRIPTION="Ubuntu 12.04 LTS"`),
}, },
}, },
{ // We don't care about the minor version of Debian { // We don't care about the minor version of Debian
ExpectedNamespace: database.Namespace{Name: "debian:7"}, ExpectedNamespace: services.Namespace{Name: "debian:7"},
Data: map[string][]byte{ Data: map[string][]byte{
"etc/lsb-release": []byte( "etc/lsb-release": []byte(
`DISTRIB_ID=Debian `DISTRIB_ID=Debian

View File

@ -19,7 +19,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/worker/detectors" "github.com/coreos/clair/worker/detectors"
) )
@ -39,7 +39,7 @@ func init() {
// Detect tries to detect OS/Version using "/etc/os-release" and "/usr/lib/os-release" // Detect tries to detect OS/Version using "/etc/os-release" and "/usr/lib/os-release"
// Typically for Debian / Ubuntu // Typically for Debian / Ubuntu
// /etc/debian_version can't be used, it does not make any difference between testing and unstable, it returns stretch/sid // /etc/debian_version can't be used, it does not make any difference between testing and unstable, it returns stretch/sid
func (detector *OsReleaseNamespaceDetector) Detect(data map[string][]byte) *database.Namespace { func (detector *OsReleaseNamespaceDetector) Detect(data map[string][]byte) *services.Namespace {
var OS, version string var OS, version string
for _, filePath := range detector.GetRequiredFiles() { for _, filePath := range detector.GetRequiredFiles() {
@ -65,7 +65,7 @@ func (detector *OsReleaseNamespaceDetector) Detect(data map[string][]byte) *data
} }
if OS != "" && version != "" { if OS != "" && version != "" {
return &database.Namespace{Name: OS + ":" + version} return &services.Namespace{Name: OS + ":" + version}
} }
return nil return nil
} }

View File

@ -17,13 +17,13 @@ package osrelease
import ( import (
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/worker/detectors/namespace" "github.com/coreos/clair/worker/detectors/namespace"
) )
var osReleaseOSTests = []namespace.NamespaceTest{ var osReleaseOSTests = []namespace.NamespaceTest{
{ {
ExpectedNamespace: database.Namespace{Name: "debian:8"}, ExpectedNamespace: services.Namespace{Name: "debian:8"},
Data: map[string][]byte{ Data: map[string][]byte{
"etc/os-release": []byte( "etc/os-release": []byte(
`PRETTY_NAME="Debian GNU/Linux 8 (jessie)" `PRETTY_NAME="Debian GNU/Linux 8 (jessie)"
@ -37,7 +37,7 @@ BUG_REPORT_URL="https://bugs.debian.org/"`),
}, },
}, },
{ {
ExpectedNamespace: database.Namespace{Name: "ubuntu:15.10"}, ExpectedNamespace: services.Namespace{Name: "ubuntu:15.10"},
Data: map[string][]byte{ Data: map[string][]byte{
"etc/os-release": []byte( "etc/os-release": []byte(
`NAME="Ubuntu" `NAME="Ubuntu"
@ -52,7 +52,7 @@ BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`),
}, },
}, },
{ // Doesn't have quotes around VERSION_ID { // Doesn't have quotes around VERSION_ID
ExpectedNamespace: database.Namespace{Name: "fedora:20"}, ExpectedNamespace: services.Namespace{Name: "fedora:20"},
Data: map[string][]byte{ Data: map[string][]byte{
"etc/os-release": []byte( "etc/os-release": []byte(
`NAME=Fedora `NAME=Fedora

View File

@ -18,7 +18,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/worker/detectors" "github.com/coreos/clair/worker/detectors"
) )
@ -37,7 +37,7 @@ func init() {
detectors.RegisterNamespaceDetector("redhat-release", &RedhatReleaseNamespaceDetector{}) detectors.RegisterNamespaceDetector("redhat-release", &RedhatReleaseNamespaceDetector{})
} }
func (detector *RedhatReleaseNamespaceDetector) Detect(data map[string][]byte) *database.Namespace { func (detector *RedhatReleaseNamespaceDetector) Detect(data map[string][]byte) *services.Namespace {
for _, filePath := range detector.GetRequiredFiles() { for _, filePath := range detector.GetRequiredFiles() {
f, hasFile := data[filePath] f, hasFile := data[filePath]
if !hasFile { if !hasFile {
@ -46,7 +46,7 @@ func (detector *RedhatReleaseNamespaceDetector) Detect(data map[string][]byte) *
r := redhatReleaseRegexp.FindStringSubmatch(string(f)) r := redhatReleaseRegexp.FindStringSubmatch(string(f))
if len(r) == 4 { if len(r) == 4 {
return &database.Namespace{Name: strings.ToLower(r[1]) + ":" + r[3]} return &services.Namespace{Name: strings.ToLower(r[1]) + ":" + r[3]}
} }
} }

View File

@ -17,19 +17,19 @@ package redhatrelease
import ( import (
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/worker/detectors/namespace" "github.com/coreos/clair/worker/detectors/namespace"
) )
var redhatReleaseTests = []namespace.NamespaceTest{ var redhatReleaseTests = []namespace.NamespaceTest{
{ {
ExpectedNamespace: database.Namespace{Name: "centos:6"}, ExpectedNamespace: services.Namespace{Name: "centos:6"},
Data: map[string][]byte{ Data: map[string][]byte{
"etc/centos-release": []byte(`CentOS release 6.6 (Final)`), "etc/centos-release": []byte(`CentOS release 6.6 (Final)`),
}, },
}, },
{ {
ExpectedNamespace: database.Namespace{Name: "centos:7"}, ExpectedNamespace: services.Namespace{Name: "centos:7"},
Data: map[string][]byte{ Data: map[string][]byte{
"etc/system-release": []byte(`CentOS Linux release 7.1.1503 (Core)`), "etc/system-release": []byte(`CentOS Linux release 7.1.1503 (Core)`),
}, },

View File

@ -17,14 +17,14 @@ package namespace
import ( import (
"testing" "testing"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/worker/detectors" "github.com/coreos/clair/worker/detectors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
type NamespaceTest struct { type NamespaceTest struct {
Data map[string][]byte Data map[string][]byte
ExpectedNamespace database.Namespace ExpectedNamespace services.Namespace
} }
func TestNamespaceDetector(t *testing.T, detector detectors.NamespaceDetector, tests []NamespaceTest) { func TestNamespaceDetector(t *testing.T, detector detectors.NamespaceDetector, tests []NamespaceTest) {

View File

@ -19,7 +19,8 @@ package worker
import ( import (
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
"github.com/coreos/clair/database" "github.com/coreos/clair/services"
"github.com/coreos/clair/services/vulnerabilities"
"github.com/coreos/clair/utils" "github.com/coreos/clair/utils"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/worker/detectors" "github.com/coreos/clair/worker/detectors"
@ -50,7 +51,7 @@ var (
// then stores everything in the database. // then stores everything in the database.
// TODO(Quentin-M): We could have a goroutine that looks for layers that have been analyzed with an // TODO(Quentin-M): We could have a goroutine that looks for layers that have been analyzed with an
// older engine version and that processes them. // older engine version and that processes them.
func Process(datastore database.Datastore, imageFormat, name, parentName, path string, headers map[string]string) error { func Process(ls vulnerabilities.Service, imageFormat, name, parentName, path string, headers map[string]string) error {
// Verify parameters. // Verify parameters.
if name == "" { if name == "" {
return cerrors.NewBadRequestError("could not process a layer which does not have a name") return cerrors.NewBadRequestError("could not process a layer which does not have a name")
@ -68,19 +69,19 @@ func Process(datastore database.Datastore, imageFormat, name, parentName, path s
name, utils.CleanURL(path), Version, parentName, imageFormat) name, utils.CleanURL(path), Version, parentName, imageFormat)
// Check to see if the layer is already in the database. // Check to see if the layer is already in the database.
layer, err := datastore.FindLayer(name, false, false) layer, err := ls.FindLayer(name, false, false)
if err != nil && err != cerrors.ErrNotFound { if err != nil && err != cerrors.ErrNotFound {
return err return err
} }
if err == cerrors.ErrNotFound { if err == cerrors.ErrNotFound {
// New layer case. // New layer case.
layer = database.Layer{Name: name, EngineVersion: Version} layer = services.Layer{Name: name, EngineVersion: Version}
// Retrieve the parent if it has one. // Retrieve the parent if it has one.
// We need to get it with its Features in order to diff them. // We need to get it with its Features in order to diff them.
if parentName != "" { if parentName != "" {
parent, err := datastore.FindLayer(parentName, true, false) parent, err := ls.FindLayer(parentName, true, false)
if err != nil && err != cerrors.ErrNotFound { if err != nil && err != cerrors.ErrNotFound {
return err return err
} }
@ -109,11 +110,11 @@ func Process(datastore database.Datastore, imageFormat, name, parentName, path s
return err return err
} }
return datastore.InsertLayer(layer) return ls.InsertLayer(layer)
} }
// detectContent downloads a layer's archive and extracts its Namespace and Features. // detectContent downloads a layer's archive and extracts its Namespace and Features.
func detectContent(imageFormat, name, path string, headers map[string]string, parent *database.Layer) (namespace *database.Namespace, featureVersions []database.FeatureVersion, err error) { func detectContent(imageFormat, name, path string, headers map[string]string, parent *services.Layer) (namespace *services.Namespace, featureVersions []services.FeatureVersion, err error) {
data, err := detectors.DetectData(imageFormat, path, headers, append(detectors.GetRequiredFilesFeatures(), detectors.GetRequiredFilesNamespace()...), maxFileSize) data, err := detectors.DetectData(imageFormat, path, headers, append(detectors.GetRequiredFilesFeatures(), detectors.GetRequiredFilesNamespace()...), maxFileSize)
if err != nil { if err != nil {
log.Errorf("layer %s: failed to extract data from %s: %s", name, utils.CleanURL(path), err) log.Errorf("layer %s: failed to extract data from %s: %s", name, utils.CleanURL(path), err)
@ -135,7 +136,7 @@ func detectContent(imageFormat, name, path string, headers map[string]string, pa
return return
} }
func detectNamespace(name string, data map[string][]byte, parent *database.Layer) (namespace *database.Namespace) { func detectNamespace(name string, data map[string][]byte, parent *services.Layer) (namespace *services.Namespace) {
// Use registered detectors to get the Namespace. // Use registered detectors to get the Namespace.
namespace = detectors.DetectNamespace(data) namespace = detectors.DetectNamespace(data)
if namespace != nil { if namespace != nil {
@ -155,7 +156,7 @@ func detectNamespace(name string, data map[string][]byte, parent *database.Layer
return return
} }
func detectFeatureVersions(name string, data map[string][]byte, namespace *database.Namespace, parent *database.Layer) (features []database.FeatureVersion, err error) { func detectFeatureVersions(name string, data map[string][]byte, namespace *services.Namespace, parent *services.Layer) (features []services.FeatureVersion, err error) {
// TODO(Quentin-M): We need to pass the parent image to DetectFeatures because it's possible that // TODO(Quentin-M): We need to pass the parent image to DetectFeatures because it's possible that
// some detectors would need it in order to produce the entire feature list (if they can only // some detectors would need it in order to produce the entire feature list (if they can only
// detect a diff). Also, we should probably pass the detected namespace so detectors could // detect a diff). Also, we should probably pass the detected namespace so detectors could
@ -175,7 +176,7 @@ func detectFeatureVersions(name string, data map[string][]byte, namespace *datab
} }
// Build a map of the namespaces for each FeatureVersion in our parent layer. // Build a map of the namespaces for each FeatureVersion in our parent layer.
parentFeatureNamespaces := make(map[string]database.Namespace) parentFeatureNamespaces := make(map[string]services.Namespace)
if parent != nil { if parent != nil {
for _, parentFeature := range parent.Features { for _, parentFeature := range parent.Features {
parentFeatureNamespaces[parentFeature.Feature.Name+":"+parentFeature.Version.String()] = parentFeature.Feature.Namespace parentFeatureNamespaces[parentFeature.Feature.Name+":"+parentFeature.Version.String()] = parentFeature.Feature.Namespace

View File

@ -22,6 +22,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
"github.com/coreos/clair/services"
cerrors "github.com/coreos/clair/utils/errors" cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/utils/types" "github.com/coreos/clair/utils/types"
@ -33,13 +34,13 @@ import (
) )
type mockDatastore struct { type mockDatastore struct {
database.MockDatastore database.MockVulnerabilities
layers map[string]database.Layer layers map[string]services.Layer
} }
func newMockDatastore() *mockDatastore { func newMockDatastore() *mockDatastore {
return &mockDatastore{ return &mockDatastore{
layers: make(map[string]database.Layer), layers: make(map[string]services.Layer),
} }
} }
@ -49,27 +50,27 @@ func TestProcessWithDistUpgrade(t *testing.T) {
// Create a mock datastore. // Create a mock datastore.
datastore := newMockDatastore() datastore := newMockDatastore()
datastore.FctInsertLayer = func(layer database.Layer) error { datastore.FctInsertLayer = func(layer services.Layer) error {
datastore.layers[layer.Name] = layer datastore.layers[layer.Name] = layer
return nil return nil
} }
datastore.FctFindLayer = func(name string, withFeatures, withVulnerabilities bool) (database.Layer, error) { datastore.FctFindLayer = func(name string, withFeatures, withVulnerabilities bool) (services.Layer, error) {
if layer, exists := datastore.layers[name]; exists { if layer, exists := datastore.layers[name]; exists {
return layer, nil return layer, nil
} }
return database.Layer{}, cerrors.ErrNotFound return services.Layer{}, cerrors.ErrNotFound
} }
// Create the list of FeatureVersions that should not been upgraded from one layer to another. // Create the list of FeatureVersions that should not been upgraded from one layer to another.
nonUpgradedFeatureVersions := []database.FeatureVersion{ nonUpgradedFeatureVersions := []services.FeatureVersion{
{Feature: database.Feature{Name: "libtext-wrapi18n-perl"}, Version: types.NewVersionUnsafe("0.06-7")}, {Feature: services.Feature{Name: "libtext-wrapi18n-perl"}, Version: types.NewVersionUnsafe("0.06-7")},
{Feature: database.Feature{Name: "libtext-charwidth-perl"}, Version: types.NewVersionUnsafe("0.04-7")}, {Feature: services.Feature{Name: "libtext-charwidth-perl"}, Version: types.NewVersionUnsafe("0.04-7")},
{Feature: database.Feature{Name: "libtext-iconv-perl"}, Version: types.NewVersionUnsafe("1.7-5")}, {Feature: services.Feature{Name: "libtext-iconv-perl"}, Version: types.NewVersionUnsafe("1.7-5")},
{Feature: database.Feature{Name: "mawk"}, Version: types.NewVersionUnsafe("1.3.3-17")}, {Feature: services.Feature{Name: "mawk"}, Version: types.NewVersionUnsafe("1.3.3-17")},
{Feature: database.Feature{Name: "insserv"}, Version: types.NewVersionUnsafe("1.14.0-5")}, {Feature: services.Feature{Name: "insserv"}, Version: types.NewVersionUnsafe("1.14.0-5")},
{Feature: database.Feature{Name: "db"}, Version: types.NewVersionUnsafe("5.1.29-5")}, {Feature: services.Feature{Name: "db"}, Version: types.NewVersionUnsafe("5.1.29-5")},
{Feature: database.Feature{Name: "ustr"}, Version: types.NewVersionUnsafe("1.0.4-3")}, {Feature: services.Feature{Name: "ustr"}, Version: types.NewVersionUnsafe("1.0.4-3")},
{Feature: database.Feature{Name: "xz-utils"}, Version: types.NewVersionUnsafe("5.1.1alpha+20120614-2")}, {Feature: services.Feature{Name: "xz-utils"}, Version: types.NewVersionUnsafe("5.1.1alpha+20120614-2")},
} }
// Process test layers. // Process test layers.