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:
parent
951efed1ff
commit
c823c39da5
@ -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
36
Dockerfile.test
Normal 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"]
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
38
clair.go
38
clair.go
@ -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)
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
|
317
database/mock.go
317
database/mock.go
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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"),
|
||||||
|
@ -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++ {
|
||||||
|
@ -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()
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
|
@ -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 ¬ification
|
return ¬ification
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
@ -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 {
|
||||||
|
61
services/keyvalue/keyvalue.go
Normal file
61
services/keyvalue/keyvalue.go
Normal 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
69
services/locks/locks.go
Normal 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)
|
||||||
|
}
|
@ -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
|
79
services/notifications/notifications.go
Normal file
79
services/notifications/notifications.go
Normal 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
39
services/services.go
Normal 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()
|
||||||
|
}
|
@ -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{
|
113
services/vulnerabilities/vulnerabilities.go
Normal file
113
services/vulnerabilities/vulnerabilities.go
Normal 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
|
||||||
|
}
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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"),
|
||||||
|
@ -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,
|
||||||
|
@ -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"),
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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...)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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]}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)`),
|
||||||
},
|
},
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user