API: drop v1 api, changed v2 api for Clair v3.

pull/432/head
Sida Chen 7 years ago
parent 6c9a131b09
commit a378cb070c

@ -35,11 +35,9 @@ const timeoutResponse = `{"Error":{"Message":"Clair failed to respond within the
// Config is the configuration for the API service. // Config is the configuration for the API service.
type Config struct { type Config struct {
Port int
GrpcPort int GrpcPort int
HealthPort int HealthPort int
Timeout time.Duration Timeout time.Duration
PaginationKey string
CertFile, KeyFile, CAFile string CertFile, KeyFile, CAFile string
} }
@ -51,40 +49,7 @@ func RunV2(cfg *Config, store database.Datastore) {
if tlsConfig != nil { if tlsConfig != nil {
log.Info("main API configured with client certificate authentication") log.Info("main API configured with client certificate authentication")
} }
v2.Run(cfg.GrpcPort, tlsConfig, cfg.PaginationKey, cfg.CertFile, cfg.KeyFile, store) v2.Run(cfg.GrpcPort, tlsConfig, cfg.CertFile, cfg.KeyFile, store)
}
func Run(cfg *Config, store database.Datastore, st *stopper.Stopper) {
defer st.End()
// Do not run the API service if there is no config.
if cfg == nil {
log.Info("main API service is disabled.")
return
}
log.WithField("port", cfg.Port).Info("starting main API")
tlsConfig, err := tlsClientConfig(cfg.CAFile)
if err != nil {
log.WithError(err).Fatal("could not initialize client cert authentication")
}
if tlsConfig != nil {
log.Info("main API configured with client certificate authentication")
}
srv := &graceful.Server{
Timeout: 0, // Already handled by our TimeOut middleware
NoSignalHandling: true, // We want to use our own Stopper
Server: &http.Server{
Addr: ":" + strconv.Itoa(cfg.Port),
TLSConfig: tlsConfig,
Handler: http.TimeoutHandler(newAPIHandler(cfg, store), cfg.Timeout, timeoutResponse),
},
}
listenAndServeWithStopper(srv, st, cfg.CertFile, cfg.KeyFile)
log.Info("main API stopped")
} }
func RunHealth(cfg *Config, store database.Datastore, st *stopper.Stopper) { func RunHealth(cfg *Config, store database.Datastore, st *stopper.Stopper) {

@ -16,13 +16,9 @@ package api
import ( import (
"net/http" "net/http"
"strings"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
log "github.com/sirupsen/logrus"
"github.com/coreos/clair/api/httputil"
"github.com/coreos/clair/api/v1"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
) )
@ -30,34 +26,6 @@ import (
// depending on the API version specified in the request URI. // depending on the API version specified in the request URI.
type router map[string]*httprouter.Router type router map[string]*httprouter.Router
// Let's hope we never have more than 99 API versions.
const apiVersionLength = len("v99")
func newAPIHandler(cfg *Config, store database.Datastore) http.Handler {
router := make(router)
router["/v1"] = v1.NewRouter(store, cfg.PaginationKey)
return router
}
func (rtr router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
urlStr := r.URL.String()
var version string
if len(urlStr) >= apiVersionLength {
version = urlStr[:apiVersionLength]
}
if router, _ := rtr[version]; router != nil {
// Remove the version number from the request path to let the router do its
// job but do not update the RequestURI
r.URL.Path = strings.Replace(r.URL.Path, version, "", 1)
router.ServeHTTP(w, r)
return
}
log.WithFields(log.Fields{"status": http.StatusNotFound, "method": r.Method, "request uri": r.RequestURI, "remote addr": httputil.GetClientAddr(r)}).Info("Served HTTP request")
http.NotFound(w, r)
}
func newHealthHandler(store database.Datastore) http.Handler { func newHealthHandler(store database.Datastore) http.Handler {
router := httprouter.New() router := httprouter.New()
router.GET("/health", healthHandler(store)) router.GET("/health", healthHandler(store))

@ -1,317 +0,0 @@
// Copyright 2017 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 v1
import (
"fmt"
"github.com/coreos/clair/api/token"
"github.com/coreos/clair/database"
"github.com/coreos/clair/ext/versionfmt"
)
type Error struct {
Message string `json:"Message,omitempty"`
}
type Layer struct {
Name string `json:"Name,omitempty"`
NamespaceNames []string `json:"NamespaceNames,omitempty"`
Path string `json:"Path,omitempty"`
Headers map[string]string `json:"Headers,omitempty"`
ParentName string `json:"ParentName,omitempty"`
Format string `json:"Format,omitempty"`
IndexedByVersion int `json:"IndexedByVersion,omitempty"`
Features []Feature `json:"Features,omitempty"`
}
func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabilities bool) Layer {
layer := Layer{
Name: dbLayer.Name,
IndexedByVersion: dbLayer.EngineVersion,
}
if dbLayer.Parent != nil {
layer.ParentName = dbLayer.Parent.Name
}
for _, ns := range dbLayer.Namespaces {
layer.NamespaceNames = append(layer.NamespaceNames, ns.Name)
}
if withFeatures || withVulnerabilities && dbLayer.Features != nil {
for _, dbFeatureVersion := range dbLayer.Features {
feature := Feature{
Name: dbFeatureVersion.Feature.Name,
NamespaceName: dbFeatureVersion.Feature.Namespace.Name,
VersionFormat: dbFeatureVersion.Feature.Namespace.VersionFormat,
Version: dbFeatureVersion.Version,
AddedBy: dbFeatureVersion.AddedBy.Name,
}
for _, dbVuln := range dbFeatureVersion.AffectedBy {
vuln := Vulnerability{
Name: dbVuln.Name,
NamespaceName: dbVuln.Namespace.Name,
Description: dbVuln.Description,
Link: dbVuln.Link,
Severity: string(dbVuln.Severity),
Metadata: dbVuln.Metadata,
}
if dbVuln.FixedBy != versionfmt.MaxVersion {
vuln.FixedBy = dbVuln.FixedBy
}
feature.Vulnerabilities = append(feature.Vulnerabilities, vuln)
}
layer.Features = append(layer.Features, feature)
}
}
return layer
}
type Namespace struct {
Name string `json:"Name,omitempty"`
VersionFormat string `json:"VersionFormat,omitempty"`
}
type Vulnerability struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
Description string `json:"Description,omitempty"`
Link string `json:"Link,omitempty"`
Severity string `json:"Severity,omitempty"`
Metadata map[string]interface{} `json:"Metadata,omitempty"`
FixedBy string `json:"FixedBy,omitempty"`
FixedIn []Feature `json:"FixedIn,omitempty"`
}
func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) {
severity, err := database.NewSeverity(v.Severity)
if err != nil {
return database.Vulnerability{}, err
}
var dbFeatures []database.FeatureVersion
for _, feature := range v.FixedIn {
dbFeature, err := feature.DatabaseModel()
if err != nil {
return database.Vulnerability{}, err
}
dbFeatures = append(dbFeatures, dbFeature)
}
return database.Vulnerability{
Name: v.Name,
Namespace: database.Namespace{Name: v.NamespaceName},
Description: v.Description,
Link: v.Link,
Severity: severity,
Metadata: v.Metadata,
FixedIn: dbFeatures,
}, nil
}
func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability, withFixedIn bool) Vulnerability {
vuln := Vulnerability{
Name: dbVuln.Name,
NamespaceName: dbVuln.Namespace.Name,
Description: dbVuln.Description,
Link: dbVuln.Link,
Severity: string(dbVuln.Severity),
Metadata: dbVuln.Metadata,
}
if withFixedIn {
for _, dbFeatureVersion := range dbVuln.FixedIn {
vuln.FixedIn = append(vuln.FixedIn, FeatureFromDatabaseModel(dbFeatureVersion))
}
}
return vuln
}
type Feature struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
VersionFormat string `json:"VersionFormat,omitempty"`
Version string `json:"Version,omitempty"`
Vulnerabilities []Vulnerability `json:"Vulnerabilities,omitempty"`
AddedBy string `json:"AddedBy,omitempty"`
}
func FeatureFromDatabaseModel(dbFeatureVersion database.FeatureVersion) Feature {
version := dbFeatureVersion.Version
if version == versionfmt.MaxVersion {
version = "None"
}
return Feature{
Name: dbFeatureVersion.Feature.Name,
NamespaceName: dbFeatureVersion.Feature.Namespace.Name,
VersionFormat: dbFeatureVersion.Feature.Namespace.VersionFormat,
Version: version,
AddedBy: dbFeatureVersion.AddedBy.Name,
}
}
func (f Feature) DatabaseModel() (fv database.FeatureVersion, err error) {
var version string
if f.Version == "None" {
version = versionfmt.MaxVersion
} else {
err = versionfmt.Valid(f.VersionFormat, f.Version)
if err != nil {
return
}
version = f.Version
}
fv = database.FeatureVersion{
Feature: database.Feature{
Name: f.Name,
Namespace: database.Namespace{
Name: f.NamespaceName,
VersionFormat: f.VersionFormat,
},
},
Version: version,
}
return
}
type Notification struct {
Name string `json:"Name,omitempty"`
Created string `json:"Created,omitempty"`
Notified string `json:"Notified,omitempty"`
Deleted string `json:"Deleted,omitempty"`
Limit int `json:"Limit,omitempty"`
Page string `json:"Page,omitempty"`
NextPage string `json:"NextPage,omitempty"`
Old *VulnerabilityWithLayers `json:"Old,omitempty"`
New *VulnerabilityWithLayers `json:"New,omitempty"`
}
func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotification, limit int, pageToken string, nextPage database.VulnerabilityNotificationPageNumber, key string) Notification {
var oldVuln *VulnerabilityWithLayers
if dbNotification.OldVulnerability != nil {
v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.OldVulnerability)
oldVuln = &v
}
var newVuln *VulnerabilityWithLayers
if dbNotification.NewVulnerability != nil {
v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.NewVulnerability)
newVuln = &v
}
var nextPageStr string
if nextPage != database.NoVulnerabilityNotificationPage {
nextPageBytes, _ := token.Marshal(nextPage, key)
nextPageStr = string(nextPageBytes)
}
var created, notified, deleted string
if !dbNotification.Created.IsZero() {
created = fmt.Sprintf("%d", dbNotification.Created.Unix())
}
if !dbNotification.Notified.IsZero() {
notified = fmt.Sprintf("%d", dbNotification.Notified.Unix())
}
if !dbNotification.Deleted.IsZero() {
deleted = fmt.Sprintf("%d", dbNotification.Deleted.Unix())
}
// TODO(jzelinskie): implement "changed" key
fmt.Println(dbNotification.Deleted.IsZero())
return Notification{
Name: dbNotification.Name,
Created: created,
Notified: notified,
Deleted: deleted,
Limit: limit,
Page: pageToken,
NextPage: nextPageStr,
Old: oldVuln,
New: newVuln,
}
}
type VulnerabilityWithLayers struct {
Vulnerability *Vulnerability `json:"Vulnerability,omitempty"`
// This field is guaranteed to be in order only for pagination.
// Indices from different notifications may not be comparable.
OrderedLayersIntroducingVulnerability []OrderedLayerName `json:"OrderedLayersIntroducingVulnerability,omitempty"`
// This field is deprecated.
LayersIntroducingVulnerability []string `json:"LayersIntroducingVulnerability,omitempty"`
}
type OrderedLayerName struct {
Index int `json:"Index"`
LayerName string `json:"LayerName"`
}
func VulnerabilityWithLayersFromDatabaseModel(dbVuln database.Vulnerability) VulnerabilityWithLayers {
vuln := VulnerabilityFromDatabaseModel(dbVuln, true)
var layers []string
var orderedLayers []OrderedLayerName
for _, layer := range dbVuln.LayersIntroducingVulnerability {
layers = append(layers, layer.Name)
orderedLayers = append(orderedLayers, OrderedLayerName{
Index: layer.ID,
LayerName: layer.Name,
})
}
return VulnerabilityWithLayers{
Vulnerability: &vuln,
OrderedLayersIntroducingVulnerability: orderedLayers,
LayersIntroducingVulnerability: layers,
}
}
type LayerEnvelope struct {
Layer *Layer `json:"Layer,omitempty"`
Error *Error `json:"Error,omitempty"`
}
type NamespaceEnvelope struct {
Namespaces *[]Namespace `json:"Namespaces,omitempty"`
Error *Error `json:"Error,omitempty"`
}
type VulnerabilityEnvelope struct {
Vulnerability *Vulnerability `json:"Vulnerability,omitempty"`
Vulnerabilities *[]Vulnerability `json:"Vulnerabilities,omitempty"`
NextPage string `json:"NextPage,omitempty"`
Error *Error `json:"Error,omitempty"`
}
type NotificationEnvelope struct {
Notification *Notification `json:"Notification,omitempty"`
Error *Error `json:"Error,omitempty"`
}
type FeatureEnvelope struct {
Feature *Feature `json:"Feature,omitempty"`
Features *[]Feature `json:"Features,omitempty"`
Error *Error `json:"Error,omitempty"`
}

@ -1,100 +0,0 @@
// 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 v1 implements the first version of the Clair API.
package v1
import (
"net/http"
"strconv"
"time"
"github.com/julienschmidt/httprouter"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"github.com/coreos/clair/api/httputil"
"github.com/coreos/clair/database"
)
var (
promResponseDurationMilliseconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: "clair_api_response_duration_milliseconds",
Help: "The duration of time it takes to receieve and write a response to an API request",
Buckets: prometheus.ExponentialBuckets(9.375, 2, 10),
}, []string{"route", "code"})
)
func init() {
prometheus.MustRegister(promResponseDurationMilliseconds)
}
type handler func(http.ResponseWriter, *http.Request, httprouter.Params, *context) (route string, status int)
func httpHandler(h handler, ctx *context) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
start := time.Now()
route, status := h(w, r, p, ctx)
statusStr := strconv.Itoa(status)
if status == 0 {
statusStr = "???"
}
promResponseDurationMilliseconds.
WithLabelValues(route, statusStr).
Observe(float64(time.Since(start).Nanoseconds()) / float64(time.Millisecond))
log.WithFields(log.Fields{"remote addr": httputil.GetClientAddr(r), "method": r.Method, "request uri": r.RequestURI, "status": statusStr, "elapsed time": time.Since(start)}).Info("Handled HTTP request")
}
}
type context struct {
Store database.Datastore
PaginationKey string
}
// NewRouter creates an HTTP router for version 1 of the Clair API.
func NewRouter(store database.Datastore, paginationKey string) *httprouter.Router {
router := httprouter.New()
ctx := &context{store, paginationKey}
// Layers
router.POST("/layers", httpHandler(postLayer, ctx))
router.GET("/layers/:layerName", httpHandler(getLayer, ctx))
router.DELETE("/layers/:layerName", httpHandler(deleteLayer, ctx))
// Namespaces
router.GET("/namespaces", httpHandler(getNamespaces, ctx))
// Vulnerabilities
router.GET("/namespaces/:namespaceName/vulnerabilities", httpHandler(getVulnerabilities, ctx))
router.POST("/namespaces/:namespaceName/vulnerabilities", httpHandler(postVulnerability, ctx))
router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", httpHandler(getVulnerability, ctx))
router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", httpHandler(putVulnerability, ctx))
router.DELETE("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", httpHandler(deleteVulnerability, ctx))
// Fixes
router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes", httpHandler(getFixes, ctx))
router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes/:fixName", httpHandler(putFix, ctx))
router.DELETE("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes/:fixName", httpHandler(deleteFix, ctx))
// Notifications
router.GET("/notifications/:notificationName", httpHandler(getNotification, ctx))
router.DELETE("/notifications/:notificationName", httpHandler(deleteNotification, ctx))
// Metrics
router.GET("/metrics", httpHandler(getMetrics, ctx))
return router
}

@ -1,503 +0,0 @@
// 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 v1
import (
"compress/gzip"
"encoding/json"
"io"
"net/http"
"strconv"
"strings"
"github.com/julienschmidt/httprouter"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"github.com/coreos/clair"
"github.com/coreos/clair/api/token"
"github.com/coreos/clair/database"
"github.com/coreos/clair/pkg/commonerr"
"github.com/coreos/clair/pkg/tarutil"
)
const (
// These are the route identifiers for prometheus.
postLayerRoute = "v1/postLayer"
getLayerRoute = "v1/getLayer"
deleteLayerRoute = "v1/deleteLayer"
getNamespacesRoute = "v1/getNamespaces"
getVulnerabilitiesRoute = "v1/getVulnerabilities"
postVulnerabilityRoute = "v1/postVulnerability"
getVulnerabilityRoute = "v1/getVulnerability"
putVulnerabilityRoute = "v1/putVulnerability"
deleteVulnerabilityRoute = "v1/deleteVulnerability"
getFixesRoute = "v1/getFixes"
putFixRoute = "v1/putFix"
deleteFixRoute = "v1/deleteFix"
getNotificationRoute = "v1/getNotification"
deleteNotificationRoute = "v1/deleteNotification"
getMetricsRoute = "v1/getMetrics"
// maxBodySize restricts client request bodies to 1MiB.
maxBodySize int64 = 1048576
// statusUnprocessableEntity represents the 422 (Unprocessable Entity) status code, which means
// the server understands the content type of the request entity
// (hence a 415(Unsupported Media Type) status code is inappropriate), and the syntax of the
// request entity is correct (thus a 400 (Bad Request) status code is inappropriate) but was
// unable to process the contained instructions.
statusUnprocessableEntity = 422
)
func decodeJSON(r *http.Request, v interface{}) error {
defer r.Body.Close()
return json.NewDecoder(io.LimitReader(r.Body, maxBodySize)).Decode(v)
}
func writeResponse(w http.ResponseWriter, r *http.Request, status int, resp interface{}) {
// Headers must be written before the response.
header := w.Header()
header.Set("Content-Type", "application/json;charset=utf-8")
header.Set("Server", "clair")
// Gzip the response if the client supports it.
var writer io.Writer = w
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
gzipWriter := gzip.NewWriter(w)
defer gzipWriter.Close()
writer = gzipWriter
header.Set("Content-Encoding", "gzip")
}
// Write the response.
w.WriteHeader(status)
err := json.NewEncoder(writer).Encode(resp)
if err != nil {
switch err.(type) {
case *json.MarshalerError, *json.UnsupportedTypeError, *json.UnsupportedValueError:
panic("v1: failed to marshal response: " + err.Error())
default:
log.WithError(err).Warning("failed to write response")
}
}
}
func postLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
request := LayerEnvelope{}
err := decodeJSON(r, &request)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, LayerEnvelope{Error: &Error{err.Error()}})
return postLayerRoute, http.StatusBadRequest
}
if request.Layer == nil {
writeResponse(w, r, http.StatusBadRequest, LayerEnvelope{Error: &Error{"failed to provide layer"}})
return postLayerRoute, http.StatusBadRequest
}
err = clair.ProcessLayer(ctx.Store, request.Layer.Format, request.Layer.Name, request.Layer.ParentName, request.Layer.Path, request.Layer.Headers)
if err != nil {
if err == tarutil.ErrCouldNotExtract ||
err == tarutil.ErrExtractedFileTooBig ||
err == clair.ErrUnsupported {
writeResponse(w, r, statusUnprocessableEntity, LayerEnvelope{Error: &Error{err.Error()}})
return postLayerRoute, statusUnprocessableEntity
}
if _, badreq := err.(*commonerr.ErrBadRequest); badreq {
writeResponse(w, r, http.StatusBadRequest, LayerEnvelope{Error: &Error{err.Error()}})
return postLayerRoute, http.StatusBadRequest
}
writeResponse(w, r, http.StatusInternalServerError, LayerEnvelope{Error: &Error{err.Error()}})
return postLayerRoute, http.StatusInternalServerError
}
writeResponse(w, r, http.StatusCreated, LayerEnvelope{Layer: &Layer{
Name: request.Layer.Name,
ParentName: request.Layer.ParentName,
Path: request.Layer.Path,
Headers: request.Layer.Headers,
Format: request.Layer.Format,
IndexedByVersion: clair.Version,
}})
return postLayerRoute, http.StatusCreated
}
func getLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
_, withFeatures := r.URL.Query()["features"]
_, withVulnerabilities := r.URL.Query()["vulnerabilities"]
dbLayer, err := ctx.Store.FindLayer(p.ByName("layerName"), withFeatures, withVulnerabilities)
if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, LayerEnvelope{Error: &Error{err.Error()}})
return getLayerRoute, http.StatusNotFound
} else if err != nil {
writeResponse(w, r, http.StatusInternalServerError, LayerEnvelope{Error: &Error{err.Error()}})
return getLayerRoute, http.StatusInternalServerError
}
layer := LayerFromDatabaseModel(dbLayer, withFeatures, withVulnerabilities)
writeResponse(w, r, http.StatusOK, LayerEnvelope{Layer: &layer})
return getLayerRoute, http.StatusOK
}
func deleteLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
err := ctx.Store.DeleteLayer(p.ByName("layerName"))
if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, LayerEnvelope{Error: &Error{err.Error()}})
return deleteLayerRoute, http.StatusNotFound
} else if err != nil {
writeResponse(w, r, http.StatusInternalServerError, LayerEnvelope{Error: &Error{err.Error()}})
return deleteLayerRoute, http.StatusInternalServerError
}
w.WriteHeader(http.StatusOK)
return deleteLayerRoute, http.StatusOK
}
func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
dbNamespaces, err := ctx.Store.ListNamespaces()
if err != nil {
writeResponse(w, r, http.StatusInternalServerError, NamespaceEnvelope{Error: &Error{err.Error()}})
return getNamespacesRoute, http.StatusInternalServerError
}
var namespaces []Namespace
for _, dbNamespace := range dbNamespaces {
namespaces = append(namespaces, Namespace{
Name: dbNamespace.Name,
VersionFormat: dbNamespace.VersionFormat,
})
}
writeResponse(w, r, http.StatusOK, NamespaceEnvelope{Namespaces: &namespaces})
return getNamespacesRoute, http.StatusOK
}
func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
query := r.URL.Query()
limitStrs, limitExists := query["limit"]
if !limitExists {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"must provide limit query parameter"}})
return getVulnerabilitiesRoute, http.StatusBadRequest
}
limit, err := strconv.Atoi(limitStrs[0])
if err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid limit format: " + err.Error()}})
return getVulnerabilitiesRoute, http.StatusBadRequest
} else if limit < 0 {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"limit value should not be less than zero"}})
return getVulnerabilitiesRoute, http.StatusBadRequest
}
page := 0
pageStrs, pageExists := query["page"]
if pageExists {
err = token.Unmarshal(pageStrs[0], ctx.PaginationKey, &page)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest
}
}
namespace := p.ByName("namespaceName")
if namespace == "" {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"namespace should not be empty"}})
return getNotificationRoute, http.StatusBadRequest
}
dbVulns, nextPage, err := ctx.Store.ListVulnerabilities(namespace, limit, page)
if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return getVulnerabilityRoute, http.StatusNotFound
} else if err != nil {
writeResponse(w, r, http.StatusInternalServerError, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return getVulnerabilitiesRoute, http.StatusInternalServerError
}
var vulns []Vulnerability
for _, dbVuln := range dbVulns {
vuln := VulnerabilityFromDatabaseModel(dbVuln, false)
vulns = append(vulns, vuln)
}
var nextPageStr string
if nextPage != -1 {
nextPageBytes, err := token.Marshal(nextPage, ctx.PaginationKey)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest
}
nextPageStr = string(nextPageBytes)
}
writeResponse(w, r, http.StatusOK, VulnerabilityEnvelope{Vulnerabilities: &vulns, NextPage: nextPageStr})
return getVulnerabilitiesRoute, http.StatusOK
}
func postVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
request := VulnerabilityEnvelope{}
err := decodeJSON(r, &request)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return postVulnerabilityRoute, http.StatusBadRequest
}
if request.Vulnerability == nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"failed to provide vulnerability"}})
return postVulnerabilityRoute, http.StatusBadRequest
}
vuln, err := request.Vulnerability.DatabaseModel()
if err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return postVulnerabilityRoute, http.StatusBadRequest
}
err = ctx.Store.InsertVulnerabilities([]database.Vulnerability{vuln}, true)
if err != nil {
switch err.(type) {
case *commonerr.ErrBadRequest:
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return postVulnerabilityRoute, http.StatusBadRequest
default:
writeResponse(w, r, http.StatusInternalServerError, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return postVulnerabilityRoute, http.StatusInternalServerError
}
}
writeResponse(w, r, http.StatusCreated, VulnerabilityEnvelope{Vulnerability: request.Vulnerability})
return postVulnerabilityRoute, http.StatusCreated
}
func getVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
_, withFixedIn := r.URL.Query()["fixedIn"]
dbVuln, err := ctx.Store.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return getVulnerabilityRoute, http.StatusNotFound
} else if err != nil {
writeResponse(w, r, http.StatusInternalServerError, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return getVulnerabilityRoute, http.StatusInternalServerError
}
vuln := VulnerabilityFromDatabaseModel(dbVuln, withFixedIn)
writeResponse(w, r, http.StatusOK, VulnerabilityEnvelope{Vulnerability: &vuln})
return getVulnerabilityRoute, http.StatusOK
}
func putVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
request := VulnerabilityEnvelope{}
err := decodeJSON(r, &request)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return putVulnerabilityRoute, http.StatusBadRequest
}
if request.Vulnerability == nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"failed to provide vulnerability"}})
return putVulnerabilityRoute, http.StatusBadRequest
}
if len(request.Vulnerability.FixedIn) != 0 {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"Vulnerability.FixedIn must be empty"}})
return putVulnerabilityRoute, http.StatusBadRequest
}
vuln, err := request.Vulnerability.DatabaseModel()
if err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return putVulnerabilityRoute, http.StatusBadRequest
}
vuln.Namespace.Name = p.ByName("namespaceName")
vuln.Name = p.ByName("vulnerabilityName")
err = ctx.Store.InsertVulnerabilities([]database.Vulnerability{vuln}, true)
if err != nil {
switch err.(type) {
case *commonerr.ErrBadRequest:
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return putVulnerabilityRoute, http.StatusBadRequest
default:
writeResponse(w, r, http.StatusInternalServerError, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return putVulnerabilityRoute, http.StatusInternalServerError
}
}
writeResponse(w, r, http.StatusOK, VulnerabilityEnvelope{Vulnerability: request.Vulnerability})
return putVulnerabilityRoute, http.StatusOK
}
func deleteVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
err := ctx.Store.DeleteVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return deleteVulnerabilityRoute, http.StatusNotFound
} else if err != nil {
writeResponse(w, r, http.StatusInternalServerError, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return deleteVulnerabilityRoute, http.StatusInternalServerError
}
w.WriteHeader(http.StatusOK)
return deleteVulnerabilityRoute, http.StatusOK
}
func getFixes(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
dbVuln, err := ctx.Store.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}})
return getFixesRoute, http.StatusNotFound
} else if err != nil {
writeResponse(w, r, http.StatusInternalServerError, FeatureEnvelope{Error: &Error{err.Error()}})
return getFixesRoute, http.StatusInternalServerError
}
vuln := VulnerabilityFromDatabaseModel(dbVuln, true)
writeResponse(w, r, http.StatusOK, FeatureEnvelope{Features: &vuln.FixedIn})
return getFixesRoute, http.StatusOK
}
func putFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
request := FeatureEnvelope{}
err := decodeJSON(r, &request)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, FeatureEnvelope{Error: &Error{err.Error()}})
return putFixRoute, http.StatusBadRequest
}
if request.Feature == nil {
writeResponse(w, r, http.StatusBadRequest, FeatureEnvelope{Error: &Error{"failed to provide feature"}})
return putFixRoute, http.StatusBadRequest
}
if request.Feature.Name != p.ByName("fixName") {
writeResponse(w, r, http.StatusBadRequest, FeatureEnvelope{Error: &Error{"feature name in URL and JSON do not match"}})
return putFixRoute, http.StatusBadRequest
}
dbFix, err := request.Feature.DatabaseModel()
if err != nil {
writeResponse(w, r, http.StatusBadRequest, FeatureEnvelope{Error: &Error{err.Error()}})
return putFixRoute, http.StatusBadRequest
}
err = ctx.Store.InsertVulnerabilityFixes(p.ByName("vulnerabilityNamespace"), p.ByName("vulnerabilityName"), []database.FeatureVersion{dbFix})
if err != nil {
switch err.(type) {
case *commonerr.ErrBadRequest:
writeResponse(w, r, http.StatusBadRequest, FeatureEnvelope{Error: &Error{err.Error()}})
return putFixRoute, http.StatusBadRequest
default:
if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}})
return putFixRoute, http.StatusNotFound
}
writeResponse(w, r, http.StatusInternalServerError, FeatureEnvelope{Error: &Error{err.Error()}})
return putFixRoute, http.StatusInternalServerError
}
}
writeResponse(w, r, http.StatusOK, FeatureEnvelope{Feature: request.Feature})
return putFixRoute, http.StatusOK
}
func deleteFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
err := ctx.Store.DeleteVulnerabilityFix(p.ByName("vulnerabilityNamespace"), p.ByName("vulnerabilityName"), p.ByName("fixName"))
if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}})
return deleteFixRoute, http.StatusNotFound
} else if err != nil {
writeResponse(w, r, http.StatusInternalServerError, FeatureEnvelope{Error: &Error{err.Error()}})
return deleteFixRoute, http.StatusInternalServerError
}
w.WriteHeader(http.StatusOK)
return deleteFixRoute, http.StatusOK
}
func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
query := r.URL.Query()
limitStrs, limitExists := query["limit"]
if !limitExists {
writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"must provide limit query parameter"}})
return getNotificationRoute, http.StatusBadRequest
}
limit, err := strconv.Atoi(limitStrs[0])
if err != nil {
writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid limit format: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest
}
var pageToken string
page := database.VulnerabilityNotificationFirstPage
pageStrs, pageExists := query["page"]
if pageExists {
err := token.Unmarshal(pageStrs[0], ctx.PaginationKey, &page)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest
}
pageToken = pageStrs[0]
} else {
pageTokenBytes, err := token.Marshal(page, ctx.PaginationKey)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest
}
pageToken = string(pageTokenBytes)
}
dbNotification, nextPage, err := ctx.Store.GetNotification(p.ByName("notificationName"), limit, page)
if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, NotificationEnvelope{Error: &Error{err.Error()}})
return deleteNotificationRoute, http.StatusNotFound
} else if err != nil {
writeResponse(w, r, http.StatusInternalServerError, NotificationEnvelope{Error: &Error{err.Error()}})
return getNotificationRoute, http.StatusInternalServerError
}
notification := NotificationFromDatabaseModel(dbNotification, limit, pageToken, nextPage, ctx.PaginationKey)
writeResponse(w, r, http.StatusOK, NotificationEnvelope{Notification: &notification})
return getNotificationRoute, http.StatusOK
}
func deleteNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
err := ctx.Store.DeleteNotification(p.ByName("notificationName"))
if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, NotificationEnvelope{Error: &Error{err.Error()}})
return deleteNotificationRoute, http.StatusNotFound
} else if err != nil {
writeResponse(w, r, http.StatusInternalServerError, NotificationEnvelope{Error: &Error{err.Error()}})
return deleteNotificationRoute, http.StatusInternalServerError
}
w.WriteHeader(http.StatusOK)
return deleteNotificationRoute, http.StatusOK
}
func getMetrics(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
prometheus.Handler().ServeHTTP(w, r)
return getMetricsRoute, 0
}

@ -9,20 +9,20 @@ It is generated from these files:
It has these top-level messages: It has these top-level messages:
Vulnerability Vulnerability
ClairStatus
Feature Feature
Ancestry Ancestry
LayersIntroducingVulnerabilty
OrderedLayerName
Layer Layer
Notification Notification
Page IndexedAncestryName
PagedVulnerableAncestries
PostAncestryRequest PostAncestryRequest
PostAncestryResponse PostAncestryResponse
GetAncestryRequest GetAncestryRequest
GetAncestryResponse GetAncestryResponse
GetNotificationRequest GetNotificationRequest
GetNotificationResponse GetNotificationResponse
DeleteNotificationRequest MarkNotificationAsReadRequest
*/ */
package clairpb package clairpb
@ -31,6 +31,7 @@ import fmt "fmt"
import math "math" import math "math"
import _ "google.golang.org/genproto/googleapis/api/annotations" import _ "google.golang.org/genproto/googleapis/api/annotations"
import google_protobuf1 "github.com/golang/protobuf/ptypes/empty" import google_protobuf1 "github.com/golang/protobuf/ptypes/empty"
import google_protobuf2 "github.com/golang/protobuf/ptypes/timestamp"
import ( import (
context "golang.org/x/net/context" context "golang.org/x/net/context"
@ -49,14 +50,16 @@ var _ = math.Inf
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Vulnerability struct { type Vulnerability struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
NamespaceName string `protobuf:"bytes,2,opt,name=namespace_name,json=namespaceName" json:"namespace_name,omitempty"` NamespaceName string `protobuf:"bytes,2,opt,name=namespace_name,json=namespaceName" json:"namespace_name,omitempty"`
Description string `protobuf:"bytes,3,opt,name=description" json:"description,omitempty"` Description string `protobuf:"bytes,3,opt,name=description" json:"description,omitempty"`
Link string `protobuf:"bytes,4,opt,name=link" json:"link,omitempty"` Link string `protobuf:"bytes,4,opt,name=link" json:"link,omitempty"`
Severity string `protobuf:"bytes,5,opt,name=severity" json:"severity,omitempty"` Severity string `protobuf:"bytes,5,opt,name=severity" json:"severity,omitempty"`
Metadata string `protobuf:"bytes,6,opt,name=metadata" json:"metadata,omitempty"` Metadata string `protobuf:"bytes,6,opt,name=metadata" json:"metadata,omitempty"`
FixedBy string `protobuf:"bytes,7,opt,name=fixed_by,json=fixedBy" json:"fixed_by,omitempty"` // fixed_by exists when vulnerability is under feature.
FixedInFeatures []*Feature `protobuf:"bytes,8,rep,name=fixed_in_features,json=fixedInFeatures" json:"fixed_in_features,omitempty"` FixedBy string `protobuf:"bytes,7,opt,name=fixed_by,json=fixedBy" json:"fixed_by,omitempty"`
// affected_versions exists when vulnerability is under notification.
AffectedVersions []*Feature `protobuf:"bytes,8,rep,name=affected_versions,json=affectedVersions" json:"affected_versions,omitempty"`
} }
func (m *Vulnerability) Reset() { *m = Vulnerability{} } func (m *Vulnerability) Reset() { *m = Vulnerability{} }
@ -113,9 +116,43 @@ func (m *Vulnerability) GetFixedBy() string {
return "" return ""
} }
func (m *Vulnerability) GetFixedInFeatures() []*Feature { func (m *Vulnerability) GetAffectedVersions() []*Feature {
if m != nil { if m != nil {
return m.FixedInFeatures return m.AffectedVersions
}
return nil
}
type ClairStatus struct {
// listers and detectors are processors implemented in this Clair and used to
// scan ancestries
Listers []string `protobuf:"bytes,1,rep,name=listers" json:"listers,omitempty"`
Detectors []string `protobuf:"bytes,2,rep,name=detectors" json:"detectors,omitempty"`
LastUpdateTime *google_protobuf2.Timestamp `protobuf:"bytes,3,opt,name=last_update_time,json=lastUpdateTime" json:"last_update_time,omitempty"`
}
func (m *ClairStatus) Reset() { *m = ClairStatus{} }
func (m *ClairStatus) String() string { return proto.CompactTextString(m) }
func (*ClairStatus) ProtoMessage() {}
func (*ClairStatus) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *ClairStatus) GetListers() []string {
if m != nil {
return m.Listers
}
return nil
}
func (m *ClairStatus) GetDetectors() []string {
if m != nil {
return m.Detectors
}
return nil
}
func (m *ClairStatus) GetLastUpdateTime() *google_protobuf2.Timestamp {
if m != nil {
return m.LastUpdateTime
} }
return nil return nil
} }
@ -125,14 +162,13 @@ type Feature struct {
NamespaceName string `protobuf:"bytes,2,opt,name=namespace_name,json=namespaceName" json:"namespace_name,omitempty"` NamespaceName string `protobuf:"bytes,2,opt,name=namespace_name,json=namespaceName" json:"namespace_name,omitempty"`
Version string `protobuf:"bytes,3,opt,name=version" json:"version,omitempty"` Version string `protobuf:"bytes,3,opt,name=version" json:"version,omitempty"`
VersionFormat string `protobuf:"bytes,4,opt,name=version_format,json=versionFormat" json:"version_format,omitempty"` VersionFormat string `protobuf:"bytes,4,opt,name=version_format,json=versionFormat" json:"version_format,omitempty"`
AddedBy string `protobuf:"bytes,5,opt,name=added_by,json=addedBy" json:"added_by,omitempty"` Vulnerabilities []*Vulnerability `protobuf:"bytes,5,rep,name=vulnerabilities" json:"vulnerabilities,omitempty"`
Vulnerabilities []*Vulnerability `protobuf:"bytes,6,rep,name=vulnerabilities" json:"vulnerabilities,omitempty"`
} }
func (m *Feature) Reset() { *m = Feature{} } func (m *Feature) Reset() { *m = Feature{} }
func (m *Feature) String() string { return proto.CompactTextString(m) } func (m *Feature) String() string { return proto.CompactTextString(m) }
func (*Feature) ProtoMessage() {} func (*Feature) ProtoMessage() {}
func (*Feature) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } func (*Feature) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (m *Feature) GetName() string { func (m *Feature) GetName() string {
if m != nil { if m != nil {
@ -162,13 +198,6 @@ func (m *Feature) GetVersionFormat() string {
return "" return ""
} }
func (m *Feature) GetAddedBy() string {
if m != nil {
return m.AddedBy
}
return ""
}
func (m *Feature) GetVulnerabilities() []*Vulnerability { func (m *Feature) GetVulnerabilities() []*Vulnerability {
if m != nil { if m != nil {
return m.Vulnerabilities return m.Vulnerabilities
@ -177,15 +206,20 @@ func (m *Feature) GetVulnerabilities() []*Vulnerability {
} }
type Ancestry struct { type Ancestry struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
EngineVersion int32 `protobuf:"varint,2,opt,name=engine_version,json=engineVersion" json:"engine_version,omitempty"` Features []*Feature `protobuf:"bytes,2,rep,name=features" json:"features,omitempty"`
Layers []*Layer `protobuf:"bytes,3,rep,name=layers" json:"layers,omitempty"` Layers []*Layer `protobuf:"bytes,3,rep,name=layers" json:"layers,omitempty"`
// scanned_listers and scanned_detectors are used to scan this ancestry, it
// may be different from listers and detectors in ClairStatus since the
// ancestry could be scanned by previous version of Clair.
ScannedListers []string `protobuf:"bytes,4,rep,name=scanned_listers,json=scannedListers" json:"scanned_listers,omitempty"`
ScannedDetectors []string `protobuf:"bytes,5,rep,name=scanned_detectors,json=scannedDetectors" json:"scanned_detectors,omitempty"`
} }
func (m *Ancestry) Reset() { *m = Ancestry{} } func (m *Ancestry) Reset() { *m = Ancestry{} }
func (m *Ancestry) String() string { return proto.CompactTextString(m) } func (m *Ancestry) String() string { return proto.CompactTextString(m) }
func (*Ancestry) ProtoMessage() {} func (*Ancestry) ProtoMessage() {}
func (*Ancestry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } func (*Ancestry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func (m *Ancestry) GetName() string { func (m *Ancestry) GetName() string {
if m != nil { if m != nil {
@ -194,11 +228,11 @@ func (m *Ancestry) GetName() string {
return "" return ""
} }
func (m *Ancestry) GetEngineVersion() int32 { func (m *Ancestry) GetFeatures() []*Feature {
if m != nil { if m != nil {
return m.EngineVersion return m.Features
} }
return 0 return nil
} }
func (m *Ancestry) GetLayers() []*Layer { func (m *Ancestry) GetLayers() []*Layer {
@ -208,91 +242,49 @@ func (m *Ancestry) GetLayers() []*Layer {
return nil return nil
} }
type LayersIntroducingVulnerabilty struct { func (m *Ancestry) GetScannedListers() []string {
Vulnerability *Vulnerability `protobuf:"bytes,1,opt,name=vulnerability" json:"vulnerability,omitempty"`
Layers []*OrderedLayerName `protobuf:"bytes,2,rep,name=layers" json:"layers,omitempty"`
}
func (m *LayersIntroducingVulnerabilty) Reset() { *m = LayersIntroducingVulnerabilty{} }
func (m *LayersIntroducingVulnerabilty) String() string { return proto.CompactTextString(m) }
func (*LayersIntroducingVulnerabilty) ProtoMessage() {}
func (*LayersIntroducingVulnerabilty) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func (m *LayersIntroducingVulnerabilty) GetVulnerability() *Vulnerability {
if m != nil { if m != nil {
return m.Vulnerability return m.ScannedListers
} }
return nil return nil
} }
func (m *LayersIntroducingVulnerabilty) GetLayers() []*OrderedLayerName { func (m *Ancestry) GetScannedDetectors() []string {
if m != nil { if m != nil {
return m.Layers return m.ScannedDetectors
} }
return nil return nil
} }
type OrderedLayerName struct {
Index int32 `protobuf:"varint,1,opt,name=index" json:"index,omitempty"`
LayerName string `protobuf:"bytes,2,opt,name=layer_name,json=layerName" json:"layer_name,omitempty"`
}
func (m *OrderedLayerName) Reset() { *m = OrderedLayerName{} }
func (m *OrderedLayerName) String() string { return proto.CompactTextString(m) }
func (*OrderedLayerName) ProtoMessage() {}
func (*OrderedLayerName) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
func (m *OrderedLayerName) GetIndex() int32 {
if m != nil {
return m.Index
}
return 0
}
func (m *OrderedLayerName) GetLayerName() string {
if m != nil {
return m.LayerName
}
return ""
}
type Layer struct { type Layer struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Hash string `protobuf:"bytes,1,opt,name=hash" json:"hash,omitempty"`
NamespaceNames []string `protobuf:"bytes,2,rep,name=namespace_names,json=namespaceNames" json:"namespace_names,omitempty"`
} }
func (m *Layer) Reset() { *m = Layer{} } func (m *Layer) Reset() { *m = Layer{} }
func (m *Layer) String() string { return proto.CompactTextString(m) } func (m *Layer) String() string { return proto.CompactTextString(m) }
func (*Layer) ProtoMessage() {} func (*Layer) ProtoMessage() {}
func (*Layer) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } func (*Layer) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
func (m *Layer) GetName() string { func (m *Layer) GetHash() string {
if m != nil { if m != nil {
return m.Name return m.Hash
} }
return "" return ""
} }
func (m *Layer) GetNamespaceNames() []string {
if m != nil {
return m.NamespaceNames
}
return nil
}
type Notification struct { type Notification struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Created string `protobuf:"bytes,2,opt,name=created" json:"created,omitempty"` Created string `protobuf:"bytes,2,opt,name=created" json:"created,omitempty"`
Notified string `protobuf:"bytes,3,opt,name=notified" json:"notified,omitempty"` Notified string `protobuf:"bytes,3,opt,name=notified" json:"notified,omitempty"`
Deleted string `protobuf:"bytes,4,opt,name=deleted" json:"deleted,omitempty"` Deleted string `protobuf:"bytes,4,opt,name=deleted" json:"deleted,omitempty"`
Limit int32 `protobuf:"varint,5,opt,name=limit" json:"limit,omitempty"` Old *PagedVulnerableAncestries `protobuf:"bytes,5,opt,name=old" json:"old,omitempty"`
Page *Page `protobuf:"bytes,6,opt,name=page" json:"page,omitempty"` New *PagedVulnerableAncestries `protobuf:"bytes,6,opt,name=new" json:"new,omitempty"`
} }
func (m *Notification) Reset() { *m = Notification{} } func (m *Notification) Reset() { *m = Notification{} }
func (m *Notification) String() string { return proto.CompactTextString(m) } func (m *Notification) String() string { return proto.CompactTextString(m) }
func (*Notification) ProtoMessage() {} func (*Notification) ProtoMessage() {}
func (*Notification) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } func (*Notification) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
func (m *Notification) GetName() string { func (m *Notification) GetName() string {
if m != nil { if m != nil {
@ -322,56 +314,91 @@ func (m *Notification) GetDeleted() string {
return "" return ""
} }
func (m *Notification) GetLimit() int32 { func (m *Notification) GetOld() *PagedVulnerableAncestries {
if m != nil { if m != nil {
return m.Limit return m.Old
} }
return 0 return nil
} }
func (m *Notification) GetPage() *Page { func (m *Notification) GetNew() *PagedVulnerableAncestries {
if m != nil { if m != nil {
return m.Page return m.New
} }
return nil return nil
} }
type Page struct { type IndexedAncestryName struct {
ThisToken string `protobuf:"bytes,1,opt,name=this_token,json=thisToken" json:"this_token,omitempty"` // index is unique to name in all streams simultaneously streamed, increasing
NextToken string `protobuf:"bytes,2,opt,name=next_token,json=nextToken" json:"next_token,omitempty"` // and larger than all indexes in previous page in same stream.
Old *LayersIntroducingVulnerabilty `protobuf:"bytes,3,opt,name=old" json:"old,omitempty"` Index int32 `protobuf:"varint,1,opt,name=index" json:"index,omitempty"`
New *LayersIntroducingVulnerabilty `protobuf:"bytes,4,opt,name=new" json:"new,omitempty"` Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
} }
func (m *Page) Reset() { *m = Page{} } func (m *IndexedAncestryName) Reset() { *m = IndexedAncestryName{} }
func (m *Page) String() string { return proto.CompactTextString(m) } func (m *IndexedAncestryName) String() string { return proto.CompactTextString(m) }
func (*Page) ProtoMessage() {} func (*IndexedAncestryName) ProtoMessage() {}
func (*Page) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } func (*IndexedAncestryName) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
func (m *Page) GetThisToken() string { func (m *IndexedAncestryName) GetIndex() int32 {
if m != nil { if m != nil {
return m.ThisToken return m.Index
}
return 0
}
func (m *IndexedAncestryName) GetName() string {
if m != nil {
return m.Name
} }
return "" return ""
} }
func (m *Page) GetNextToken() string { type PagedVulnerableAncestries struct {
CurrentPage string `protobuf:"bytes,1,opt,name=current_page,json=currentPage" json:"current_page,omitempty"`
// if next_page is empty, it signals the end of all pages.
NextPage string `protobuf:"bytes,2,opt,name=next_page,json=nextPage" json:"next_page,omitempty"`
Limit int32 `protobuf:"varint,3,opt,name=limit" json:"limit,omitempty"`
Vulnerability *Vulnerability `protobuf:"bytes,4,opt,name=vulnerability" json:"vulnerability,omitempty"`
Ancestries []*IndexedAncestryName `protobuf:"bytes,5,rep,name=ancestries" json:"ancestries,omitempty"`
}
func (m *PagedVulnerableAncestries) Reset() { *m = PagedVulnerableAncestries{} }
func (m *PagedVulnerableAncestries) String() string { return proto.CompactTextString(m) }
func (*PagedVulnerableAncestries) ProtoMessage() {}
func (*PagedVulnerableAncestries) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
func (m *PagedVulnerableAncestries) GetCurrentPage() string {
if m != nil { if m != nil {
return m.NextToken return m.CurrentPage
} }
return "" return ""
} }
func (m *Page) GetOld() *LayersIntroducingVulnerabilty { func (m *PagedVulnerableAncestries) GetNextPage() string {
if m != nil { if m != nil {
return m.Old return m.NextPage
}
return ""
}
func (m *PagedVulnerableAncestries) GetLimit() int32 {
if m != nil {
return m.Limit
}
return 0
}
func (m *PagedVulnerableAncestries) GetVulnerability() *Vulnerability {
if m != nil {
return m.Vulnerability
} }
return nil return nil
} }
func (m *Page) GetNew() *LayersIntroducingVulnerabilty { func (m *PagedVulnerableAncestries) GetAncestries() []*IndexedAncestryName {
if m != nil { if m != nil {
return m.New return m.Ancestries
} }
return nil return nil
} }
@ -409,7 +436,7 @@ func (m *PostAncestryRequest) GetLayers() []*PostAncestryRequest_PostLayer {
} }
type PostAncestryRequest_PostLayer struct { type PostAncestryRequest_PostLayer struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Hash string `protobuf:"bytes,1,opt,name=hash" json:"hash,omitempty"`
Path string `protobuf:"bytes,2,opt,name=path" json:"path,omitempty"` Path string `protobuf:"bytes,2,opt,name=path" json:"path,omitempty"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` Headers map[string]string `protobuf:"bytes,3,rep,name=headers" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
} }
@ -421,9 +448,9 @@ func (*PostAncestryRequest_PostLayer) Descriptor() ([]byte, []int) {
return fileDescriptor0, []int{8, 0} return fileDescriptor0, []int{8, 0}
} }
func (m *PostAncestryRequest_PostLayer) GetName() string { func (m *PostAncestryRequest_PostLayer) GetHash() string {
if m != nil { if m != nil {
return m.Name return m.Hash
} }
return "" return ""
} }
@ -443,7 +470,7 @@ func (m *PostAncestryRequest_PostLayer) GetHeaders() map[string]string {
} }
type PostAncestryResponse struct { type PostAncestryResponse struct {
EngineVersion int32 `protobuf:"varint,1,opt,name=engine_version,json=engineVersion" json:"engine_version,omitempty"` Status *ClairStatus `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"`
} }
func (m *PostAncestryResponse) Reset() { *m = PostAncestryResponse{} } func (m *PostAncestryResponse) Reset() { *m = PostAncestryResponse{} }
@ -451,11 +478,11 @@ func (m *PostAncestryResponse) String() string { return proto.Compact
func (*PostAncestryResponse) ProtoMessage() {} func (*PostAncestryResponse) ProtoMessage() {}
func (*PostAncestryResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } func (*PostAncestryResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
func (m *PostAncestryResponse) GetEngineVersion() int32 { func (m *PostAncestryResponse) GetStatus() *ClairStatus {
if m != nil { if m != nil {
return m.EngineVersion return m.Status
} }
return 0 return nil
} }
type GetAncestryRequest struct { type GetAncestryRequest struct {
@ -491,8 +518,8 @@ func (m *GetAncestryRequest) GetWithFeatures() bool {
} }
type GetAncestryResponse struct { type GetAncestryResponse struct {
Ancestry *Ancestry `protobuf:"bytes,1,opt,name=ancestry" json:"ancestry,omitempty"` Ancestry *Ancestry `protobuf:"bytes,1,opt,name=ancestry" json:"ancestry,omitempty"`
Features []*Feature `protobuf:"bytes,2,rep,name=features" json:"features,omitempty"` Status *ClairStatus `protobuf:"bytes,2,opt,name=status" json:"status,omitempty"`
} }
func (m *GetAncestryResponse) Reset() { *m = GetAncestryResponse{} } func (m *GetAncestryResponse) Reset() { *m = GetAncestryResponse{} }
@ -507,17 +534,19 @@ func (m *GetAncestryResponse) GetAncestry() *Ancestry {
return nil return nil
} }
func (m *GetAncestryResponse) GetFeatures() []*Feature { func (m *GetAncestryResponse) GetStatus() *ClairStatus {
if m != nil { if m != nil {
return m.Features return m.Status
} }
return nil return nil
} }
type GetNotificationRequest struct { type GetNotificationRequest struct {
Page string `protobuf:"bytes,1,opt,name=page" json:"page,omitempty"` // if the vulnerability_page is empty, it implies the first page.
Limit int32 `protobuf:"varint,2,opt,name=limit" json:"limit,omitempty"` OldVulnerabilityPage string `protobuf:"bytes,1,opt,name=old_vulnerability_page,json=oldVulnerabilityPage" json:"old_vulnerability_page,omitempty"`
Name string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"` NewVulnerabilityPage string `protobuf:"bytes,2,opt,name=new_vulnerability_page,json=newVulnerabilityPage" json:"new_vulnerability_page,omitempty"`
Limit int32 `protobuf:"varint,3,opt,name=limit" json:"limit,omitempty"`
Name string `protobuf:"bytes,4,opt,name=name" json:"name,omitempty"`
} }
func (m *GetNotificationRequest) Reset() { *m = GetNotificationRequest{} } func (m *GetNotificationRequest) Reset() { *m = GetNotificationRequest{} }
@ -525,9 +554,16 @@ func (m *GetNotificationRequest) String() string { return proto.Compa
func (*GetNotificationRequest) ProtoMessage() {} func (*GetNotificationRequest) ProtoMessage() {}
func (*GetNotificationRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } func (*GetNotificationRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} }
func (m *GetNotificationRequest) GetPage() string { func (m *GetNotificationRequest) GetOldVulnerabilityPage() string {
if m != nil {
return m.OldVulnerabilityPage
}
return ""
}
func (m *GetNotificationRequest) GetNewVulnerabilityPage() string {
if m != nil { if m != nil {
return m.Page return m.NewVulnerabilityPage
} }
return "" return ""
} }
@ -562,16 +598,16 @@ func (m *GetNotificationResponse) GetNotification() *Notification {
return nil return nil
} }
type DeleteNotificationRequest struct { type MarkNotificationAsReadRequest struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
} }
func (m *DeleteNotificationRequest) Reset() { *m = DeleteNotificationRequest{} } func (m *MarkNotificationAsReadRequest) Reset() { *m = MarkNotificationAsReadRequest{} }
func (m *DeleteNotificationRequest) String() string { return proto.CompactTextString(m) } func (m *MarkNotificationAsReadRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteNotificationRequest) ProtoMessage() {} func (*MarkNotificationAsReadRequest) ProtoMessage() {}
func (*DeleteNotificationRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } func (*MarkNotificationAsReadRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} }
func (m *DeleteNotificationRequest) GetName() string { func (m *MarkNotificationAsReadRequest) GetName() string {
if m != nil { if m != nil {
return m.Name return m.Name
} }
@ -580,13 +616,13 @@ func (m *DeleteNotificationRequest) GetName() string {
func init() { func init() {
proto.RegisterType((*Vulnerability)(nil), "clairpb.Vulnerability") proto.RegisterType((*Vulnerability)(nil), "clairpb.Vulnerability")
proto.RegisterType((*ClairStatus)(nil), "clairpb.ClairStatus")
proto.RegisterType((*Feature)(nil), "clairpb.Feature") proto.RegisterType((*Feature)(nil), "clairpb.Feature")
proto.RegisterType((*Ancestry)(nil), "clairpb.Ancestry") proto.RegisterType((*Ancestry)(nil), "clairpb.Ancestry")
proto.RegisterType((*LayersIntroducingVulnerabilty)(nil), "clairpb.LayersIntroducingVulnerabilty")
proto.RegisterType((*OrderedLayerName)(nil), "clairpb.OrderedLayerName")
proto.RegisterType((*Layer)(nil), "clairpb.Layer") proto.RegisterType((*Layer)(nil), "clairpb.Layer")
proto.RegisterType((*Notification)(nil), "clairpb.Notification") proto.RegisterType((*Notification)(nil), "clairpb.Notification")
proto.RegisterType((*Page)(nil), "clairpb.Page") proto.RegisterType((*IndexedAncestryName)(nil), "clairpb.IndexedAncestryName")
proto.RegisterType((*PagedVulnerableAncestries)(nil), "clairpb.PagedVulnerableAncestries")
proto.RegisterType((*PostAncestryRequest)(nil), "clairpb.PostAncestryRequest") proto.RegisterType((*PostAncestryRequest)(nil), "clairpb.PostAncestryRequest")
proto.RegisterType((*PostAncestryRequest_PostLayer)(nil), "clairpb.PostAncestryRequest.PostLayer") proto.RegisterType((*PostAncestryRequest_PostLayer)(nil), "clairpb.PostAncestryRequest.PostLayer")
proto.RegisterType((*PostAncestryResponse)(nil), "clairpb.PostAncestryResponse") proto.RegisterType((*PostAncestryResponse)(nil), "clairpb.PostAncestryResponse")
@ -594,7 +630,7 @@ func init() {
proto.RegisterType((*GetAncestryResponse)(nil), "clairpb.GetAncestryResponse") proto.RegisterType((*GetAncestryResponse)(nil), "clairpb.GetAncestryResponse")
proto.RegisterType((*GetNotificationRequest)(nil), "clairpb.GetNotificationRequest") proto.RegisterType((*GetNotificationRequest)(nil), "clairpb.GetNotificationRequest")
proto.RegisterType((*GetNotificationResponse)(nil), "clairpb.GetNotificationResponse") proto.RegisterType((*GetNotificationResponse)(nil), "clairpb.GetNotificationResponse")
proto.RegisterType((*DeleteNotificationRequest)(nil), "clairpb.DeleteNotificationRequest") proto.RegisterType((*MarkNotificationAsReadRequest)(nil), "clairpb.MarkNotificationAsReadRequest")
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.
@ -706,7 +742,7 @@ var _AncestryService_serviceDesc = grpc.ServiceDesc{
type NotificationServiceClient interface { type NotificationServiceClient interface {
GetNotification(ctx context.Context, in *GetNotificationRequest, opts ...grpc.CallOption) (*GetNotificationResponse, error) GetNotification(ctx context.Context, in *GetNotificationRequest, opts ...grpc.CallOption) (*GetNotificationResponse, error)
DeleteNotification(ctx context.Context, in *DeleteNotificationRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) MarkNotificationAsRead(ctx context.Context, in *MarkNotificationAsReadRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error)
} }
type notificationServiceClient struct { type notificationServiceClient struct {
@ -726,9 +762,9 @@ func (c *notificationServiceClient) GetNotification(ctx context.Context, in *Get
return out, nil return out, nil
} }
func (c *notificationServiceClient) DeleteNotification(ctx context.Context, in *DeleteNotificationRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { func (c *notificationServiceClient) MarkNotificationAsRead(ctx context.Context, in *MarkNotificationAsReadRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) {
out := new(google_protobuf1.Empty) out := new(google_protobuf1.Empty)
err := grpc.Invoke(ctx, "/clairpb.NotificationService/DeleteNotification", in, out, c.cc, opts...) err := grpc.Invoke(ctx, "/clairpb.NotificationService/MarkNotificationAsRead", in, out, c.cc, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -739,7 +775,7 @@ func (c *notificationServiceClient) DeleteNotification(ctx context.Context, in *
type NotificationServiceServer interface { type NotificationServiceServer interface {
GetNotification(context.Context, *GetNotificationRequest) (*GetNotificationResponse, error) GetNotification(context.Context, *GetNotificationRequest) (*GetNotificationResponse, error)
DeleteNotification(context.Context, *DeleteNotificationRequest) (*google_protobuf1.Empty, error) MarkNotificationAsRead(context.Context, *MarkNotificationAsReadRequest) (*google_protobuf1.Empty, error)
} }
func RegisterNotificationServiceServer(s *grpc.Server, srv NotificationServiceServer) { func RegisterNotificationServiceServer(s *grpc.Server, srv NotificationServiceServer) {
@ -764,20 +800,20 @@ func _NotificationService_GetNotification_Handler(srv interface{}, ctx context.C
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _NotificationService_DeleteNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _NotificationService_MarkNotificationAsRead_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteNotificationRequest) in := new(MarkNotificationAsReadRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
return nil, err return nil, err
} }
if interceptor == nil { if interceptor == nil {
return srv.(NotificationServiceServer).DeleteNotification(ctx, in) return srv.(NotificationServiceServer).MarkNotificationAsRead(ctx, in)
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/clairpb.NotificationService/DeleteNotification", FullMethod: "/clairpb.NotificationService/MarkNotificationAsRead",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NotificationServiceServer).DeleteNotification(ctx, req.(*DeleteNotificationRequest)) return srv.(NotificationServiceServer).MarkNotificationAsRead(ctx, req.(*MarkNotificationAsReadRequest))
} }
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
@ -791,8 +827,8 @@ var _NotificationService_serviceDesc = grpc.ServiceDesc{
Handler: _NotificationService_GetNotification_Handler, Handler: _NotificationService_GetNotification_Handler,
}, },
{ {
MethodName: "DeleteNotification", MethodName: "MarkNotificationAsRead",
Handler: _NotificationService_DeleteNotification_Handler, Handler: _NotificationService_MarkNotificationAsRead_Handler,
}, },
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
@ -802,71 +838,78 @@ var _NotificationService_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("clair.proto", fileDescriptor0) } func init() { proto.RegisterFile("clair.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 1042 bytes of a gzipped FileDescriptorProto // 1156 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0xdd, 0x6e, 0x1b, 0x45, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4d, 0x6f, 0xdb, 0x46,
0x14, 0xd6, 0xda, 0x71, 0x6c, 0x1f, 0xdb, 0x49, 0x3a, 0x49, 0xd3, 0x8d, 0x93, 0x88, 0x74, 0x11, 0x13, 0x06, 0x25, 0xcb, 0x92, 0x46, 0xf2, 0xd7, 0x5a, 0x51, 0x68, 0xd9, 0x46, 0x1c, 0xbe, 0x78,
0xa5, 0xaa, 0xc0, 0x56, 0xd3, 0x9b, 0x12, 0x01, 0x82, 0xa8, 0x6d, 0xa8, 0x04, 0xa5, 0x5a, 0xaa, 0xd3, 0x20, 0x6d, 0x25, 0x54, 0xf6, 0xa1, 0x35, 0xd2, 0x8f, 0xa4, 0x4e, 0xd2, 0x02, 0x49, 0x10,
0x5c, 0x70, 0x63, 0x4d, 0xbc, 0x27, 0xce, 0x28, 0xeb, 0x59, 0xb3, 0x3b, 0x76, 0x62, 0x55, 0xdc, 0x30, 0xa9, 0x0f, 0xbd, 0x08, 0x6b, 0x72, 0x64, 0x13, 0xa6, 0x48, 0x96, 0xbb, 0xb2, 0x2c, 0x04,
0xf0, 0x04, 0x54, 0x3c, 0x06, 0x2f, 0xc0, 0x15, 0x2f, 0xd1, 0x27, 0x00, 0xf1, 0x16, 0xdc, 0xa0, 0xbd, 0xb4, 0xc7, 0x9e, 0xda, 0xfe, 0x8f, 0xfe, 0x84, 0x5e, 0x0b, 0xf4, 0x9a, 0x7b, 0x81, 0x02,
0xf9, 0xf5, 0xae, 0x63, 0x23, 0x7e, 0xae, 0x3c, 0xe7, 0x7c, 0xe7, 0xe7, 0x3b, 0x3f, 0x33, 0x5e, 0xbd, 0xf6, 0x3f, 0x14, 0xbb, 0xdc, 0xa5, 0x48, 0x89, 0x0e, 0x8c, 0xf6, 0x24, 0xce, 0xcc, 0x33,
0x68, 0xf4, 0x63, 0xca, 0xd2, 0xce, 0x28, 0x4d, 0x44, 0x42, 0xaa, 0x4a, 0x18, 0x9d, 0xb5, 0xf7, 0xbb, 0x33, 0xcf, 0x33, 0x3b, 0x10, 0x34, 0x1c, 0x9f, 0x7a, 0x71, 0x37, 0x8a, 0x43, 0x1e, 0x92,
0x06, 0x49, 0x32, 0x88, 0xb1, 0x4b, 0x47, 0xac, 0x4b, 0x39, 0x4f, 0x04, 0x15, 0x2c, 0xe1, 0x99, 0xaa, 0x34, 0xa2, 0x93, 0xce, 0xce, 0x69, 0x18, 0x9e, 0xfa, 0xd8, 0xa3, 0x91, 0xd7, 0xa3, 0x41,
0x36, 0x6b, 0xef, 0x1a, 0x54, 0x49, 0x67, 0xe3, 0xf3, 0x2e, 0x0e, 0x47, 0x62, 0xaa, 0xc1, 0xe0, 0x10, 0x72, 0xca, 0xbd, 0x30, 0x60, 0x09, 0xac, 0xb3, 0xad, 0xa2, 0xd2, 0x3a, 0x19, 0x0f, 0x7b,
0x4d, 0x09, 0x5a, 0xa7, 0xe3, 0x98, 0x63, 0x4a, 0xcf, 0x58, 0xcc, 0xc4, 0x94, 0x10, 0x58, 0xe1, 0x38, 0x8a, 0xf8, 0x54, 0x05, 0x6f, 0xcd, 0x07, 0xb9, 0x37, 0x42, 0xc6, 0xe9, 0x28, 0x4a, 0x00,
0x74, 0x88, 0xbe, 0x77, 0xe0, 0xdd, 0xaf, 0x87, 0xea, 0x4c, 0xde, 0x83, 0x35, 0xf9, 0x9b, 0x8d, 0xd6, 0x4f, 0x25, 0x58, 0x39, 0x1e, 0xfb, 0x01, 0xc6, 0xf4, 0xc4, 0xf3, 0x3d, 0x3e, 0x25, 0x04,
0x68, 0x1f, 0x7b, 0x0a, 0x2d, 0x29, 0xb4, 0xe5, 0xb4, 0x2f, 0xa4, 0xd9, 0x01, 0x34, 0x22, 0xcc, 0x96, 0x02, 0x3a, 0x42, 0xd3, 0xd8, 0x33, 0xee, 0xd6, 0x6d, 0xf9, 0x4d, 0xfe, 0x0f, 0xab, 0xe2,
0xfa, 0x29, 0x1b, 0xc9, 0xfc, 0x7e, 0x59, 0xd9, 0xe4, 0x55, 0x32, 0x78, 0xcc, 0xf8, 0xa5, 0xbf, 0x97, 0x45, 0xd4, 0xc1, 0x81, 0x8c, 0x96, 0x64, 0x74, 0x25, 0xf5, 0x3e, 0x17, 0xb0, 0x3d, 0x68,
0xa2, 0x83, 0xcb, 0x33, 0x69, 0x43, 0x2d, 0xc3, 0x09, 0xa6, 0x4c, 0x4c, 0xfd, 0x8a, 0xd2, 0x3b, 0xb8, 0xc8, 0x9c, 0xd8, 0x8b, 0x44, 0x81, 0x66, 0x59, 0x62, 0xb2, 0x2e, 0x71, 0xb8, 0xef, 0x05,
0x59, 0x62, 0x43, 0x14, 0x34, 0xa2, 0x82, 0xfa, 0xab, 0x1a, 0xb3, 0x32, 0xd9, 0x81, 0xda, 0x39, 0xe7, 0xe6, 0x52, 0x72, 0xb8, 0xf8, 0x26, 0x1d, 0xa8, 0x31, 0xbc, 0xc0, 0xd8, 0xe3, 0x53, 0xb3,
0xbb, 0xc6, 0xa8, 0x77, 0x36, 0xf5, 0xab, 0x0a, 0xab, 0x2a, 0xf9, 0x78, 0x4a, 0x3e, 0x86, 0x5b, 0x22, 0xfd, 0xa9, 0x2d, 0x62, 0x23, 0xe4, 0xd4, 0xa5, 0x9c, 0x9a, 0xcb, 0x49, 0x4c, 0xdb, 0x64,
0x1a, 0x62, 0xbc, 0x77, 0x8e, 0x54, 0x8c, 0x53, 0xcc, 0xfc, 0xda, 0x41, 0xf9, 0x7e, 0xe3, 0x70, 0x0b, 0x6a, 0x43, 0xef, 0x12, 0xdd, 0xc1, 0xc9, 0xd4, 0xac, 0xca, 0x58, 0x55, 0xda, 0x0f, 0xa7,
0xa3, 0x63, 0xba, 0xd6, 0x79, 0xa6, 0x81, 0x70, 0x5d, 0x99, 0x3e, 0xe7, 0x46, 0xce, 0x82, 0xdf, 0xe4, 0x63, 0xd8, 0xa0, 0xc3, 0x21, 0x3a, 0x1c, 0xdd, 0xc1, 0x05, 0xc6, 0x4c, 0xd0, 0x65, 0xd6,
0x3d, 0xa8, 0x1a, 0xe1, 0xff, 0x74, 0xc3, 0x87, 0xea, 0x04, 0xd3, 0x6c, 0xd6, 0x09, 0x2b, 0xca, 0xf6, 0xca, 0x77, 0x1b, 0xfd, 0xf5, 0xae, 0xa2, 0xb5, 0xfb, 0x18, 0x29, 0x1f, 0xc7, 0x68, 0xaf,
0x00, 0xe6, 0xd8, 0x3b, 0x4f, 0xd2, 0x21, 0x15, 0xa6, 0x1f, 0x2d, 0xa3, 0x7d, 0xa6, 0x94, 0xb2, 0x6b, 0xe8, 0xb1, 0x42, 0x5a, 0x3f, 0x18, 0xd0, 0xf8, 0x5c, 0xa0, 0x5e, 0x72, 0xca, 0xc7, 0x8c,
0x40, 0x1a, 0x45, 0xba, 0x40, 0xdd, 0x98, 0xaa, 0x92, 0x8f, 0xa7, 0xe4, 0x33, 0x58, 0x9f, 0xe4, 0x98, 0x50, 0xf5, 0x3d, 0xc6, 0x31, 0x66, 0xa6, 0xb1, 0x57, 0x16, 0x17, 0x29, 0x93, 0xec, 0x40,
0xa6, 0xc6, 0x30, 0xf3, 0x57, 0x55, 0x79, 0xdb, 0xae, 0xbc, 0xc2, 0x54, 0xc3, 0x79, 0xf3, 0x60, 0xdd, 0x45, 0x8e, 0x0e, 0x0f, 0x63, 0x66, 0x96, 0x64, 0x6c, 0xe6, 0x20, 0x47, 0xb0, 0xee, 0x53,
0x08, 0xb5, 0xcf, 0x79, 0x1f, 0x33, 0x91, 0x2e, 0x1d, 0x39, 0xf2, 0x01, 0xe3, 0xd8, 0xb3, 0x45, 0xc6, 0x07, 0xe3, 0xc8, 0xa5, 0x1c, 0x07, 0x82, 0x7b, 0x49, 0x4a, 0xa3, 0xdf, 0xe9, 0x26, 0xc2,
0xc8, 0x22, 0x2b, 0x61, 0x4b, 0x6b, 0x4f, 0x4d, 0x29, 0xf7, 0x60, 0x35, 0xa6, 0x53, 0x4c, 0x33, 0x74, 0xb5, 0x30, 0xdd, 0x57, 0x5a, 0x18, 0x7b, 0x55, 0xe4, 0x7c, 0x25, 0x53, 0x84, 0xd3, 0xfa,
0xbf, 0xac, 0xf2, 0xaf, 0xb9, 0xfc, 0x5f, 0x4a, 0x75, 0x68, 0xd0, 0xe0, 0x47, 0x0f, 0xf6, 0x95, 0xcd, 0x80, 0xaa, 0xaa, 0xf5, 0xbf, 0x88, 0x63, 0x42, 0x55, 0x51, 0xa1, 0x84, 0xd1, 0xa6, 0x38,
0x26, 0x7b, 0xce, 0x45, 0x9a, 0x44, 0xe3, 0x3e, 0xe3, 0x83, 0x19, 0x45, 0x21, 0x67, 0xd6, 0xca, 0x40, 0x7d, 0x0e, 0x86, 0x61, 0x3c, 0xa2, 0x5c, 0xc9, 0xb3, 0xa2, 0xbc, 0x8f, 0xa5, 0x93, 0x7c,
0x73, 0x9c, 0x2a, 0x36, 0xcb, 0x0b, 0x2a, 0x1a, 0x93, 0x87, 0x8e, 0x47, 0x49, 0xf1, 0xd8, 0x71, 0x06, 0x6b, 0x17, 0x99, 0x49, 0xf1, 0x90, 0x99, 0x15, 0x49, 0x69, 0x3b, 0xa5, 0x34, 0x37, 0x49,
0x6e, 0x5f, 0xa7, 0x11, 0xa6, 0x18, 0xa9, 0xe4, 0x72, 0x2e, 0x8e, 0xd2, 0x09, 0x6c, 0xcc, 0x63, 0xf6, 0x3c, 0xdc, 0xfa, 0xdd, 0x80, 0xda, 0x83, 0xc0, 0x41, 0xc6, 0xe3, 0xe2, 0x39, 0x7b, 0x0f,
0x64, 0x0b, 0x2a, 0x8c, 0x47, 0x78, 0xad, 0x92, 0x57, 0x42, 0x2d, 0x90, 0x7d, 0x00, 0xe5, 0x93, 0x6a, 0xc3, 0xa4, 0xd3, 0x84, 0xcd, 0x22, 0xb9, 0x52, 0x04, 0xb9, 0x03, 0xcb, 0x3e, 0x9d, 0x0a,
0x1f, 0x76, 0x3d, 0xb6, 0x4e, 0xc1, 0x13, 0xa8, 0xa8, 0x08, 0x0b, 0xfb, 0xf8, 0x3e, 0xac, 0x17, 0x55, 0xca, 0x12, 0xbb, 0x9a, 0x62, 0x9f, 0x0a, 0xb7, 0xad, 0xa2, 0xe4, 0x1d, 0x58, 0x63, 0x0e,
0x97, 0x45, 0x33, 0xac, 0x87, 0x6b, 0x85, 0x6d, 0xc9, 0x82, 0x9f, 0x3d, 0x68, 0xbe, 0x48, 0x04, 0x0d, 0x02, 0x74, 0x07, 0x5a, 0xc6, 0x25, 0x29, 0xd5, 0xaa, 0x72, 0x3f, 0x55, 0x6a, 0xbe, 0x0b,
0x3b, 0x67, 0x7d, 0x6a, 0xef, 0xca, 0x8d, 0x68, 0x3e, 0x54, 0xfb, 0x29, 0x52, 0x81, 0x91, 0xa1, 0x1b, 0x1a, 0x38, 0x53, 0xb5, 0x22, 0xa1, 0xeb, 0x2a, 0x70, 0xa4, 0xfd, 0xd6, 0x36, 0x54, 0xe4,
0x61, 0x45, 0x79, 0x53, 0xb8, 0xf2, 0xc6, 0xc8, 0xac, 0x9b, 0x93, 0xa5, 0x57, 0x84, 0x31, 0x4a, 0x35, 0xa2, 0x91, 0x33, 0xca, 0xce, 0x74, 0x23, 0xe2, 0xdb, 0xfa, 0xc3, 0x80, 0xe6, 0xf3, 0x90,
0x2f, 0xbd, 0x68, 0x56, 0x94, 0xf5, 0xc6, 0x6c, 0xc8, 0x84, 0xda, 0xaf, 0x4a, 0xa8, 0x05, 0x72, 0x7b, 0x43, 0xcf, 0xa1, 0x7a, 0xf0, 0x17, 0xba, 0x35, 0xa1, 0xea, 0xc4, 0x48, 0x39, 0xba, 0x4a,
0x17, 0x56, 0x46, 0x74, 0x80, 0xea, 0xc6, 0x35, 0x0e, 0x5b, 0xae, 0x95, 0x2f, 0xe9, 0x00, 0x43, 0x31, 0x6d, 0x8a, 0xb1, 0x0f, 0x64, 0x36, 0xba, 0x4a, 0xac, 0xd4, 0x16, 0x59, 0x2e, 0xfa, 0x28,
0x05, 0x05, 0xbf, 0x78, 0xb0, 0x22, 0x45, 0xd9, 0x1b, 0x71, 0xc1, 0xb2, 0x9e, 0x48, 0x2e, 0x91, 0xb2, 0x12, 0x99, 0xb4, 0x49, 0x0e, 0xa0, 0x1c, 0xfa, 0xae, 0x7c, 0x43, 0x8d, 0xbe, 0x95, 0x92,
0x1b, 0xae, 0x75, 0xa9, 0x79, 0x25, 0x15, 0x12, 0xe6, 0x78, 0x2d, 0x0c, 0x6c, 0x5a, 0x27, 0x35, 0xf1, 0x82, 0x9e, 0xa2, 0xab, 0x95, 0xf1, 0x51, 0x09, 0xe0, 0x21, 0xb3, 0x05, 0x5c, 0x64, 0x05,
0x1a, 0x7e, 0x0c, 0xe5, 0x24, 0xd6, 0x84, 0x1b, 0x87, 0xf7, 0x8a, 0xbb, 0xb3, 0x6c, 0x53, 0x42, 0x38, 0x91, 0xaf, 0xeb, 0x9a, 0x59, 0x01, 0x4e, 0xac, 0x4f, 0x61, 0xf3, 0xcb, 0xc0, 0xc5, 0x4b,
0xe9, 0x22, 0x3d, 0x39, 0x5e, 0xa9, 0x7a, 0xfe, 0x85, 0x27, 0xc7, 0xab, 0xe0, 0x6d, 0x09, 0x36, 0x74, 0xb5, 0xa0, 0x72, 0xc8, 0x5a, 0x50, 0xf1, 0x84, 0x5b, 0xf6, 0x59, 0xb1, 0x13, 0x23, 0x6d,
0x5f, 0x26, 0x99, 0xb0, 0xeb, 0x1f, 0xe2, 0x77, 0x63, 0xcc, 0x04, 0x79, 0x17, 0x5a, 0xd4, 0xa8, 0xbe, 0x34, 0x6b, 0xde, 0xfa, 0xdb, 0x80, 0xad, 0x2b, 0xef, 0x20, 0xb7, 0xa1, 0xe9, 0x8c, 0xe3,
0x7a, 0xb9, 0xc6, 0x37, 0xad, 0x52, 0x2d, 0xc8, 0x36, 0xac, 0x9a, 0x2b, 0xab, 0x6b, 0x31, 0x12, 0x18, 0x03, 0x3e, 0x88, 0xe8, 0xa9, 0xa6, 0xad, 0xa1, 0x7c, 0x22, 0x8f, 0x6c, 0x43, 0x3d, 0xc0,
0xf9, 0x74, 0xee, 0x1e, 0xcc, 0x18, 0x2d, 0x48, 0xa5, 0x74, 0x85, 0xfb, 0xd1, 0xfe, 0xd5, 0x83, 0x4b, 0x15, 0x2f, 0x29, 0x92, 0xf0, 0x32, 0x09, 0xb6, 0xa0, 0xe2, 0x7b, 0x23, 0x8f, 0x4b, 0xf6,
0xba, 0xd3, 0x2e, 0x1c, 0x3d, 0x91, 0x43, 0x11, 0x17, 0x26, 0xaf, 0x3a, 0x93, 0xaf, 0xa0, 0x7a, 0x2a, 0x76, 0x62, 0x90, 0xfb, 0xb0, 0x92, 0x1d, 0xc9, 0xa9, 0x24, 0xf0, 0xea, 0xf9, 0xcd, 0x83,
0x81, 0x34, 0x9a, 0xa5, 0x7d, 0xf4, 0xcf, 0xd2, 0x76, 0xbe, 0xd0, 0x5e, 0x4f, 0xb9, 0x44, 0x6d, 0xc9, 0x7d, 0x00, 0x9a, 0x56, 0xa8, 0x46, 0x7f, 0x27, 0x4d, 0x2d, 0x60, 0xc3, 0xce, 0xe0, 0xad,
0x8c, 0xf6, 0x11, 0x34, 0xf3, 0x00, 0xd9, 0x80, 0xf2, 0x25, 0x4e, 0x0d, 0x0b, 0x79, 0x94, 0xfb, 0x37, 0x25, 0xd8, 0x7c, 0x11, 0x32, 0xae, 0x01, 0x36, 0x7e, 0x33, 0x46, 0xc6, 0xc9, 0xff, 0x60,
0x32, 0xa1, 0xf1, 0xd8, 0x5e, 0x02, 0x2d, 0x1c, 0x95, 0x1e, 0x7b, 0xc1, 0x27, 0xb0, 0x55, 0x4c, 0x45, 0xa1, 0xa6, 0x83, 0xcc, 0x84, 0x34, 0x69, 0x96, 0xd6, 0x36, 0x2c, 0xab, 0x97, 0x99, 0x34,
0x99, 0x8d, 0x12, 0x9e, 0x2d, 0x7a, 0x47, 0xbc, 0x05, 0xef, 0x48, 0xf0, 0xc6, 0x03, 0x72, 0x82, 0xaa, 0x2c, 0xf2, 0xc9, 0xdc, 0x0b, 0xb8, 0x33, 0x93, 0x6f, 0xf1, 0x2a, 0xe9, 0xcb, 0xbd, 0x8c,
0xff, 0x6d, 0x26, 0x0f, 0x61, 0xeb, 0x8a, 0x89, 0x8b, 0xde, 0xfc, 0x8b, 0x28, 0x39, 0xd6, 0xc2, 0xce, 0xaf, 0x06, 0xd4, 0x53, 0x6f, 0xd1, 0x20, 0x0b, 0x5f, 0x44, 0xf9, 0x99, 0x96, 0x4e, 0x7c,
0x4d, 0x89, 0x9d, 0x16, 0x21, 0x19, 0x57, 0xb9, 0xb8, 0x3f, 0x87, 0xb2, 0xb2, 0x6d, 0x4a, 0xa5, 0x93, 0x67, 0x50, 0x3d, 0x43, 0xea, 0xce, 0xae, 0xdd, 0xbf, 0xde, 0xb5, 0xdd, 0x2f, 0x92, 0xac,
0xfb, 0x1f, 0x48, 0x61, 0xb3, 0x40, 0xc9, 0x54, 0xf4, 0x21, 0xd4, 0x6c, 0x7a, 0xf3, 0x46, 0xdd, 0x47, 0x81, 0x88, 0xea, 0x33, 0x3a, 0x87, 0xd0, 0xcc, 0x06, 0xc8, 0x3a, 0x94, 0xcf, 0x71, 0xaa,
0x72, 0x5d, 0x77, 0xc6, 0xce, 0x84, 0x7c, 0x00, 0x35, 0x97, 0xa5, 0xb4, 0xe4, 0x2f, 0xc8, 0x59, 0xaa, 0x10, 0x9f, 0x42, 0xcd, 0x0b, 0xea, 0x8f, 0xb5, 0xcc, 0x89, 0x71, 0x58, 0xfa, 0xd0, 0xb0,
0x04, 0xa7, 0xb0, 0x7d, 0x82, 0x22, 0xff, 0x0e, 0xd8, 0x56, 0x10, 0x73, 0x29, 0x3d, 0x3b, 0xff, 0x8e, 0xa0, 0x95, 0xbf, 0x92, 0x45, 0x61, 0xc0, 0xc4, 0x22, 0x59, 0x66, 0x72, 0x77, 0xcb, 0x63,
0x01, 0xce, 0xae, 0x6f, 0x29, 0x7f, 0x7d, 0xed, 0xf6, 0x94, 0x67, 0xdb, 0x13, 0xbc, 0x82, 0x3b, 0x1a, 0xfd, 0x56, 0x5a, 0x61, 0x66, 0xaf, 0xdb, 0x0a, 0x63, 0xfd, 0x68, 0x00, 0x79, 0x82, 0xff,
0x37, 0xe2, 0x9a, 0x7a, 0x3e, 0x82, 0x26, 0xcf, 0xe9, 0x4d, 0x4d, 0xb7, 0x1d, 0xc9, 0x82, 0x53, 0x4e, 0x9a, 0x0f, 0xa0, 0x35, 0xf1, 0xf8, 0xd9, 0x60, 0x7e, 0x35, 0x8a, 0x52, 0x6b, 0xf6, 0xa6,
0xc1, 0x34, 0xe8, 0xc2, 0xce, 0x13, 0xf5, 0x92, 0x2c, 0x21, 0x3c, 0xbf, 0xc4, 0x87, 0xbf, 0x79, 0x88, 0x1d, 0xe7, 0x43, 0xe2, 0x5c, 0x99, 0x92, 0xae, 0xba, 0xb2, 0xc4, 0x36, 0x85, 0x53, 0x6d,
0xb0, 0x6e, 0x7b, 0xf4, 0x0d, 0xa6, 0x13, 0xd6, 0x47, 0x42, 0xa1, 0x99, 0xdf, 0x1c, 0xb2, 0xf7, 0x39, 0x66, 0xc5, 0xb0, 0x99, 0x2b, 0x49, 0x35, 0xf6, 0x3e, 0xd4, 0xf4, 0xf5, 0xaa, 0xb5, 0x8d,
0x77, 0x3b, 0xdc, 0xde, 0x5f, 0x82, 0xea, 0x62, 0x82, 0xad, 0x1f, 0xde, 0xfe, 0xf1, 0x53, 0x69, 0xb4, 0xb5, 0x14, 0x9c, 0x42, 0x32, 0x3c, 0x94, 0xae, 0xc1, 0xc3, 0x2f, 0x06, 0xb4, 0x9f, 0x20,
0x2d, 0xa8, 0x77, 0xed, 0x00, 0x8e, 0xbc, 0x07, 0xe4, 0x12, 0x1a, 0xb9, 0x49, 0x92, 0x5d, 0x17, 0xcf, 0x2e, 0x2e, 0xcd, 0xc5, 0x01, 0xb4, 0x43, 0xdf, 0xcd, 0x75, 0x39, 0xcd, 0x3e, 0xcd, 0x56,
0xe3, 0xe6, 0xca, 0xb5, 0xf7, 0x16, 0x83, 0x26, 0xfe, 0x5d, 0x15, 0x7f, 0x97, 0xec, 0xb8, 0xf8, 0xe8, 0xbb, 0xb9, 0xd7, 0x23, 0x9f, 0xe1, 0x01, 0xb4, 0x03, 0x9c, 0x14, 0x65, 0x25, 0x4a, 0xb6,
0xdd, 0xd7, 0x85, 0x0d, 0xfd, 0xfe, 0xf0, 0x4f, 0x0f, 0x36, 0xf3, 0xfd, 0xb0, 0x75, 0x66, 0xb0, 0x02, 0x9c, 0x2c, 0x66, 0x15, 0x3f, 0x5e, 0xbd, 0x44, 0x96, 0x32, 0x4b, 0xe4, 0x15, 0xdc, 0x5c,
0x3e, 0x37, 0x02, 0xf2, 0x4e, 0x3e, 0xd7, 0x82, 0x1e, 0xb6, 0x0f, 0x96, 0x1b, 0x18, 0x42, 0xfb, 0xa8, 0x57, 0x11, 0xf5, 0x11, 0x34, 0x83, 0x8c, 0x5f, 0x91, 0x75, 0x23, 0xed, 0x3f, 0x97, 0x94,
0x8a, 0xd0, 0x1d, 0x72, 0xbb, 0x9b, 0x9f, 0x4c, 0xd6, 0x7d, 0xad, 0xc8, 0x90, 0x04, 0xc8, 0xcd, 0x83, 0x5a, 0xfb, 0xb0, 0xfb, 0x8c, 0xc6, 0xe7, 0x59, 0xc4, 0x03, 0x66, 0x23, 0x75, 0x35, 0x19,
0x09, 0x91, 0xc0, 0x85, 0x5d, 0x3a, 0xbe, 0xf6, 0x76, 0x47, 0x7f, 0x37, 0x76, 0xec, 0x77, 0x63, 0x05, 0xcb, 0xbc, 0xff, 0xa7, 0x01, 0x6b, 0x5a, 0x80, 0x97, 0x18, 0x5f, 0x78, 0x0e, 0x12, 0x0a,
0xe7, 0xa9, 0xfc, 0x6e, 0xb4, 0x09, 0x1f, 0x2c, 0x4e, 0x78, 0x5c, 0xff, 0xd6, 0x7e, 0x96, 0x9e, 0xcd, 0xec, 0x74, 0x92, 0x9d, 0xb7, 0xbd, 0x93, 0xce, 0xee, 0x15, 0xd1, 0xa4, 0x21, 0xab, 0xf5,
0xad, 0x2a, 0xcf, 0x47, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x5b, 0x9c, 0x1d, 0xc4, 0xb5, 0x0a, 0xdd, 0x9b, 0xbf, 0x7e, 0x2e, 0xad, 0x5a, 0xf5, 0x9e, 0x56, 0xf7, 0xd0, 0xb8, 0x47, 0xce, 0xa1,
0x00, 0x00, 0x91, 0x19, 0x13, 0xb2, 0x9d, 0x9e, 0xb1, 0x38, 0xcf, 0x9d, 0x9d, 0xe2, 0xa0, 0x3a, 0xff, 0xb6,
0x3c, 0x7f, 0x9b, 0x6c, 0xa5, 0xe7, 0xf7, 0x5e, 0xe7, 0xc6, 0xff, 0xdb, 0xfe, 0xf7, 0x25, 0xd8,
0xcc, 0xb2, 0xa2, 0xfb, 0x64, 0xb0, 0x36, 0x27, 0x03, 0xb9, 0x95, 0xbd, 0xab, 0x60, 0xa0, 0x3a,
0x7b, 0x57, 0x03, 0x54, 0x41, 0xbb, 0xb2, 0xa0, 0x9b, 0xe4, 0x46, 0x2f, 0xab, 0x0e, 0xeb, 0xbd,
0x96, 0xc5, 0x90, 0x09, 0xb4, 0x8b, 0x55, 0x22, 0xb3, 0x2d, 0xf8, 0x56, 0x19, 0x3b, 0xed, 0x85,
0x3f, 0x61, 0x8f, 0xc4, 0x5f, 0x67, 0x7d, 0xf1, 0xbd, 0xe2, 0x8b, 0x1f, 0xd6, 0xbf, 0xd6, 0xff,
0xcc, 0x4f, 0x96, 0x65, 0xe6, 0xfe, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x6e, 0xc1, 0x1d, 0xff,
0xb8, 0x0b, 0x00, 0x00,
} }

@ -112,8 +112,8 @@ func request_NotificationService_GetNotification_0(ctx context.Context, marshale
} }
func request_NotificationService_DeleteNotification_0(ctx context.Context, marshaler runtime.Marshaler, client NotificationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { func request_NotificationService_MarkNotificationAsRead_0(ctx context.Context, marshaler runtime.Marshaler, client NotificationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteNotificationRequest var protoReq MarkNotificationAsReadRequest
var metadata runtime.ServerMetadata var metadata runtime.ServerMetadata
var ( var (
@ -134,7 +134,7 @@ func request_NotificationService_DeleteNotification_0(ctx context.Context, marsh
return nil, metadata, err return nil, metadata, err
} }
msg, err := client.DeleteNotification(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) msg, err := client.MarkNotificationAsRead(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err return msg, metadata, err
} }
@ -301,7 +301,7 @@ func RegisterNotificationServiceHandler(ctx context.Context, mux *runtime.ServeM
}) })
mux.Handle("DELETE", pattern_NotificationService_DeleteNotification_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("DELETE", pattern_NotificationService_MarkNotificationAsRead_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
if cn, ok := w.(http.CloseNotifier); ok { if cn, ok := w.(http.CloseNotifier); ok {
@ -319,14 +319,14 @@ func RegisterNotificationServiceHandler(ctx context.Context, mux *runtime.ServeM
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return return
} }
resp, md, err := request_NotificationService_DeleteNotification_0(rctx, inboundMarshaler, client, req, pathParams) resp, md, err := request_NotificationService_MarkNotificationAsRead_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md) ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil { if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return return
} }
forward_NotificationService_DeleteNotification_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) forward_NotificationService_MarkNotificationAsRead_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
}) })
@ -336,11 +336,11 @@ func RegisterNotificationServiceHandler(ctx context.Context, mux *runtime.ServeM
var ( var (
pattern_NotificationService_GetNotification_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"notifications", "name"}, "")) pattern_NotificationService_GetNotification_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"notifications", "name"}, ""))
pattern_NotificationService_DeleteNotification_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"notifications", "name"}, "")) pattern_NotificationService_MarkNotificationAsRead_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"notifications", "name"}, ""))
) )
var ( var (
forward_NotificationService_GetNotification_0 = runtime.ForwardResponseMessage forward_NotificationService_GetNotification_0 = runtime.ForwardResponseMessage
forward_NotificationService_DeleteNotification_0 = runtime.ForwardResponseMessage forward_NotificationService_MarkNotificationAsRead_0 = runtime.ForwardResponseMessage
) )

@ -18,6 +18,7 @@ option go_package = "clairpb";
package clairpb; package clairpb;
import "google/api/annotations.proto"; import "google/api/annotations.proto";
import "google/protobuf/empty.proto"; import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
message Vulnerability { message Vulnerability {
string name = 1; string name = 1;
@ -26,60 +27,72 @@ message Vulnerability {
string link = 4; string link = 4;
string severity = 5; string severity = 5;
string metadata = 6; string metadata = 6;
// fixed_by exists when vulnerability is under feature.
string fixed_by = 7; string fixed_by = 7;
repeated Feature fixed_in_features = 8; // affected_versions exists when vulnerability is under notification.
repeated Feature affected_versions = 8;
} }
message Feature { message ClairStatus {
// listers and detectors are processors implemented in this Clair and used to
// scan ancestries
repeated string listers = 1;
repeated string detectors = 2;
google.protobuf.Timestamp last_update_time = 3;
}
message Feature{
string name = 1; string name = 1;
string namespace_name = 2; string namespace_name = 2;
string version = 3; string version = 3;
string version_format = 4; string version_format = 4;
string added_by = 5; repeated Vulnerability vulnerabilities = 5;
repeated Vulnerability vulnerabilities = 6;
} }
message Ancestry { message Ancestry {
string name = 1; string name = 1;
int32 engine_version = 2; repeated Feature features = 2;
repeated Layer layers = 3; repeated Layer layers = 3;
}
message LayersIntroducingVulnerabilty { // scanned_listers and scanned_detectors are used to scan this ancestry, it
Vulnerability vulnerability = 1; // may be different from listers and detectors in ClairStatus since the
repeated OrderedLayerName layers = 2; // ancestry could be scanned by previous version of Clair.
} repeated string scanned_listers = 4;
repeated string scanned_detectors = 5;
message OrderedLayerName {
int32 index = 1;
string layer_name = 2;
} }
message Layer { message Layer {
string name = 1; string hash = 1;
repeated string namespace_names = 2;
} }
message Notification { message Notification {
string name = 1; string name = 1;
string created = 2; string created = 2;
string notified = 3; string notified = 3;
string deleted = 4; string deleted = 4;
int32 limit = 5; PagedVulnerableAncestries old = 5;
Page page = 6; PagedVulnerableAncestries new = 6;
} }
message Page { message IndexedAncestryName {
string this_token = 1; // index is unique to name in all streams simultaneously streamed, increasing
string next_token = 2; // and larger than all indexes in previous page in same stream.
LayersIntroducingVulnerabilty old = 3; int32 index = 1;
LayersIntroducingVulnerabilty new = 4; string name = 2;
} }
message PagedVulnerableAncestries {
string current_page = 1;
// if next_page is empty, it signals the end of all pages.
string next_page = 2;
int32 limit = 3;
Vulnerability vulnerability = 4;
repeated IndexedAncestryName ancestries = 5;
}
message PostAncestryRequest { message PostAncestryRequest {
message PostLayer { message PostLayer {
string name = 1; string hash = 1;
string path = 2; string path = 2;
map<string, string> headers = 3; map<string, string> headers = 3;
} }
@ -89,7 +102,7 @@ message PostAncestryRequest {
} }
message PostAncestryResponse { message PostAncestryResponse {
int32 engine_version = 1; ClairStatus status = 1;
} }
message GetAncestryRequest { message GetAncestryRequest {
@ -100,25 +113,25 @@ message GetAncestryRequest {
message GetAncestryResponse { message GetAncestryResponse {
Ancestry ancestry = 1; Ancestry ancestry = 1;
repeated Feature features = 2; ClairStatus status = 2;
} }
message GetNotificationRequest { message GetNotificationRequest {
string page = 1; // if the vulnerability_page is empty, it implies the first page.
int32 limit = 2; string old_vulnerability_page = 1;
string name = 3; string new_vulnerability_page = 2;
int32 limit = 3;
string name = 4;
} }
message GetNotificationResponse { message GetNotificationResponse {
Notification notification = 1; Notification notification = 1;
} }
message DeleteNotificationRequest { message MarkNotificationAsReadRequest {
string name = 1; string name = 1;
} }
service AncestryService{ service AncestryService{
rpc PostAncestry(PostAncestryRequest) returns (PostAncestryResponse) { rpc PostAncestry(PostAncestryRequest) returns (PostAncestryResponse) {
option (google.api.http) = { option (google.api.http) = {
@ -141,7 +154,7 @@ service NotificationService{
}; };
} }
rpc DeleteNotification(DeleteNotificationRequest) returns (google.protobuf.Empty) { rpc MarkNotificationAsRead(MarkNotificationAsReadRequest) returns (google.protobuf.Empty) {
option (google.api.http) = { option (google.api.http) = {
delete: "/notifications/{name}" delete: "/notifications/{name}"
}; };

@ -98,7 +98,14 @@
"type": "string" "type": "string"
}, },
{ {
"name": "page", "name": "old_vulnerability_page",
"description": "if the vulnerability_page is empty, it implies the first page.",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "new_vulnerability_page",
"in": "query", "in": "query",
"required": false, "required": false,
"type": "string" "type": "string"
@ -116,7 +123,7 @@
] ]
}, },
"delete": { "delete": {
"operationId": "DeleteNotification", "operationId": "MarkNotificationAsRead",
"responses": { "responses": {
"200": { "200": {
"description": "", "description": "",
@ -143,7 +150,7 @@
"PostAncestryRequestPostLayer": { "PostAncestryRequestPostLayer": {
"type": "object", "type": "object",
"properties": { "properties": {
"name": { "hash": {
"type": "string" "type": "string"
}, },
"path": { "path": {
@ -163,15 +170,52 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"engine_version": { "features": {
"type": "integer", "type": "array",
"format": "int32" "items": {
"$ref": "#/definitions/clairpbFeature"
}
}, },
"layers": { "layers": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/clairpbLayer" "$ref": "#/definitions/clairpbLayer"
} }
},
"scanned_listers": {
"type": "array",
"items": {
"type": "string"
},
"description": "scanned_listers and scanned_detectors are used to scan this ancestry, it\nmay be different from listers and detectors in ClairStatus since the\nancestry could be scanned by previous version of Clair."
},
"scanned_detectors": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"clairpbClairStatus": {
"type": "object",
"properties": {
"listers": {
"type": "array",
"items": {
"type": "string"
},
"title": "listers and detectors are processors implemented in this Clair and used to\nscan ancestries"
},
"detectors": {
"type": "array",
"items": {
"type": "string"
}
},
"last_update_time": {
"type": "string",
"format": "date-time"
} }
} }
}, },
@ -190,9 +234,6 @@
"version_format": { "version_format": {
"type": "string" "type": "string"
}, },
"added_by": {
"type": "string"
},
"vulnerabilities": { "vulnerabilities": {
"type": "array", "type": "array",
"items": { "items": {
@ -207,11 +248,8 @@
"ancestry": { "ancestry": {
"$ref": "#/definitions/clairpbAncestry" "$ref": "#/definitions/clairpbAncestry"
}, },
"features": { "status": {
"type": "array", "$ref": "#/definitions/clairpbClairStatus"
"items": {
"$ref": "#/definitions/clairpbFeature"
}
} }
} }
}, },
@ -223,31 +261,24 @@
} }
} }
}, },
"clairpbLayer": { "clairpbIndexedAncestryName": {
"type": "object", "type": "object",
"properties": { "properties": {
"index": {
"type": "integer",
"format": "int32",
"description": "index is unique to name in all streams simultaneously streamed, increasing\nand larger than all indexes in previous page in same stream."
},
"name": { "name": {
"type": "string" "type": "string"
},
"namespace_names": {
"type": "array",
"items": {
"type": "string"
}
} }
} }
}, },
"clairpbLayersIntroducingVulnerabilty": { "clairpbLayer": {
"type": "object", "type": "object",
"properties": { "properties": {
"vulnerability": { "hash": {
"$ref": "#/definitions/clairpbVulnerability" "type": "string"
},
"layers": {
"type": "array",
"items": {
"$ref": "#/definitions/clairpbOrderedLayerName"
}
} }
} }
}, },
@ -266,41 +297,36 @@
"deleted": { "deleted": {
"type": "string" "type": "string"
}, },
"limit": { "old": {
"type": "integer", "$ref": "#/definitions/clairpbPagedVulnerableAncestries"
"format": "int32"
},
"page": {
"$ref": "#/definitions/clairpbPage"
}
}
},
"clairpbOrderedLayerName": {
"type": "object",
"properties": {
"index": {
"type": "integer",
"format": "int32"
}, },
"layer_name": { "new": {
"type": "string" "$ref": "#/definitions/clairpbPagedVulnerableAncestries"
} }
} }
}, },
"clairpbPage": { "clairpbPagedVulnerableAncestries": {
"type": "object", "type": "object",
"properties": { "properties": {
"this_token": { "current_page": {
"type": "string" "type": "string"
}, },
"next_token": { "next_page": {
"type": "string" "type": "string",
"description": "if next_page is empty, it signals the end of all pages."
}, },
"old": { "limit": {
"$ref": "#/definitions/clairpbLayersIntroducingVulnerabilty" "type": "integer",
"format": "int32"
}, },
"new": { "vulnerability": {
"$ref": "#/definitions/clairpbLayersIntroducingVulnerabilty" "$ref": "#/definitions/clairpbVulnerability"
},
"ancestries": {
"type": "array",
"items": {
"$ref": "#/definitions/clairpbIndexedAncestryName"
}
} }
} }
}, },
@ -324,9 +350,8 @@
"clairpbPostAncestryResponse": { "clairpbPostAncestryResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"engine_version": { "status": {
"type": "integer", "$ref": "#/definitions/clairpbClairStatus"
"format": "int32"
} }
} }
}, },
@ -352,13 +377,15 @@
"type": "string" "type": "string"
}, },
"fixed_by": { "fixed_by": {
"type": "string" "type": "string",
"description": "fixed_by exists when vulnerability is under feature."
}, },
"fixed_in_features": { "affected_versions": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/clairpbFeature" "$ref": "#/definitions/clairpbFeature"
} },
"description": "affected_versions exists when vulnerability is under notification."
} }
} }
}, },

@ -18,76 +18,81 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/coreos/clair/api/token"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
"github.com/coreos/clair/ext/versionfmt" "github.com/coreos/clair/ext/versionfmt"
) )
func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotification, limit int, pageToken string, nextPage database.VulnerabilityNotificationPageNumber, key string) (*Notification, error) { // PagedVulnerableAncestriesFromDatabaseModel converts database
var oldVuln *LayersIntroducingVulnerabilty // PagedVulnerableAncestries to api PagedVulnerableAncestries and assigns
if dbNotification.OldVulnerability != nil { // indexes to ancestries.
v, err := LayersIntroducingVulnerabiltyFromDatabaseModel(*dbNotification.OldVulnerability) func PagedVulnerableAncestriesFromDatabaseModel(dbVuln *database.PagedVulnerableAncestries) (*PagedVulnerableAncestries, error) {
if err != nil { if dbVuln == nil {
return nil, err return nil, nil
}
oldVuln = v
} }
var newVuln *LayersIntroducingVulnerabilty vuln, err := VulnerabilityFromDatabaseModel(dbVuln.Vulnerability)
if dbNotification.NewVulnerability != nil { if err != nil {
v, err := LayersIntroducingVulnerabiltyFromDatabaseModel(*dbNotification.NewVulnerability) return nil, err
if err != nil { }
return nil, err
} next := ""
newVuln = v if !dbVuln.End {
next = string(dbVuln.Next)
} }
var nextPageStr string vulnAncestry := PagedVulnerableAncestries{
if nextPage != database.NoVulnerabilityNotificationPage { Vulnerability: vuln,
nextPageBytes, _ := token.Marshal(nextPage, key) CurrentPage: string(dbVuln.Current),
nextPageStr = string(nextPageBytes) NextPage: next,
Limit: int32(dbVuln.Limit),
} }
var created, notified, deleted string for index, ancestryName := range dbVuln.Affected {
indexedAncestry := IndexedAncestryName{
Name: ancestryName,
Index: int32(index),
}
vulnAncestry.Ancestries = append(vulnAncestry.Ancestries, &indexedAncestry)
}
return &vulnAncestry, nil
}
// NotificationFromDatabaseModel converts database notification, old and new
// vulnerabilities' paged vulnerable ancestries to be api notification.
func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotificationWithVulnerable) (*Notification, error) {
var (
noti Notification
err error
)
noti.Name = dbNotification.Name
if !dbNotification.Created.IsZero() { if !dbNotification.Created.IsZero() {
created = fmt.Sprintf("%d", dbNotification.Created.Unix()) noti.Created = fmt.Sprintf("%d", dbNotification.Created.Unix())
} }
if !dbNotification.Notified.IsZero() { if !dbNotification.Notified.IsZero() {
notified = fmt.Sprintf("%d", dbNotification.Notified.Unix()) noti.Notified = fmt.Sprintf("%d", dbNotification.Notified.Unix())
} }
if !dbNotification.Deleted.IsZero() { if !dbNotification.Deleted.IsZero() {
deleted = fmt.Sprintf("%d", dbNotification.Deleted.Unix()) noti.Deleted = fmt.Sprintf("%d", dbNotification.Deleted.Unix())
} }
return &Notification{
Name: dbNotification.Name,
Created: created,
Notified: notified,
Deleted: deleted,
Limit: int32(limit),
Page: &Page{
ThisToken: pageToken,
NextToken: nextPageStr,
Old: oldVuln,
New: newVuln,
},
}, nil
}
func LayersIntroducingVulnerabiltyFromDatabaseModel(dbVuln database.Vulnerability) (*LayersIntroducingVulnerabilty, error) { noti.Old, err = PagedVulnerableAncestriesFromDatabaseModel(dbNotification.Old)
vuln, err := VulnerabilityFromDatabaseModel(dbVuln, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var orderedLayers []*OrderedLayerName
return &LayersIntroducingVulnerabilty{ noti.New, err = PagedVulnerableAncestriesFromDatabaseModel(dbNotification.New)
Vulnerability: vuln, if err != nil {
Layers: orderedLayers, return nil, err
}, nil }
return &noti, nil
} }
func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability, withFixedIn bool) (*Vulnerability, error) { func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability) (*Vulnerability, error) {
metaString := "" metaString := ""
if dbVuln.Metadata != nil { if dbVuln.Metadata != nil {
metadataByte, err := json.Marshal(dbVuln.Metadata) metadataByte, err := json.Marshal(dbVuln.Metadata)
@ -97,69 +102,54 @@ func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability, withFixedIn b
metaString = string(metadataByte) metaString = string(metadataByte)
} }
vuln := Vulnerability{ return &Vulnerability{
Name: dbVuln.Name, Name: dbVuln.Name,
NamespaceName: dbVuln.Namespace.Name, NamespaceName: dbVuln.Namespace.Name,
Description: dbVuln.Description, Description: dbVuln.Description,
Link: dbVuln.Link, Link: dbVuln.Link,
Severity: string(dbVuln.Severity), Severity: string(dbVuln.Severity),
Metadata: metaString, Metadata: metaString,
} }, nil
}
if dbVuln.FixedBy != versionfmt.MaxVersion {
vuln.FixedBy = dbVuln.FixedBy
}
if withFixedIn {
for _, dbFeatureVersion := range dbVuln.FixedIn {
f, err := FeatureFromDatabaseModel(dbFeatureVersion, false)
if err != nil {
return nil, err
}
vuln.FixedInFeatures = append(vuln.FixedInFeatures, f) func VulnerabilityWithFixedInFromDatabaseModel(dbVuln database.VulnerabilityWithFixedIn) (*Vulnerability, error) {
} vuln, err := VulnerabilityFromDatabaseModel(dbVuln.Vulnerability)
if err != nil {
return nil, err
} }
return &vuln, nil vuln.FixedBy = dbVuln.FixedInVersion
return vuln, nil
} }
func LayerFromDatabaseModel(dbLayer database.Layer) *Layer { // AncestryFromDatabaseModel converts database ancestry to api ancestry.
layer := Layer{ func AncestryFromDatabaseModel(dbAncestry database.Ancestry) *Ancestry {
Name: dbLayer.Name, ancestry := &Ancestry{
Name: dbAncestry.Name,
} }
for _, ns := range dbLayer.Namespaces { for _, layer := range dbAncestry.Layers {
layer.NamespaceNames = append(layer.NamespaceNames, ns.Name) ancestry.Layers = append(ancestry.Layers, LayerFromDatabaseModel(layer))
} }
return ancestry
}
// LayerFromDatabaseModel converts database layer to api layer.
func LayerFromDatabaseModel(dbLayer database.Layer) *Layer {
layer := Layer{Hash: dbLayer.Hash}
return &layer return &layer
} }
func FeatureFromDatabaseModel(fv database.FeatureVersion, withVulnerabilities bool) (*Feature, error) { // NamespacedFeatureFromDatabaseModel converts database namespacedFeature to api Feature.
version := fv.Version func NamespacedFeatureFromDatabaseModel(feature database.NamespacedFeature) *Feature {
version := feature.Feature.Version
if version == versionfmt.MaxVersion { if version == versionfmt.MaxVersion {
version = "None" version = "None"
} }
f := &Feature{
Name: fv.Feature.Name,
NamespaceName: fv.Feature.Namespace.Name,
VersionFormat: fv.Feature.Namespace.VersionFormat,
Version: version,
AddedBy: fv.AddedBy.Name,
}
if withVulnerabilities {
for _, dbVuln := range fv.AffectedBy {
// VulnerabilityFromDatabaseModel should be called without FixedIn,
// Otherwise it might cause infinite loop
vul, err := VulnerabilityFromDatabaseModel(dbVuln, false)
if err != nil {
return nil, err
}
f.Vulnerabilities = append(f.Vulnerabilities, vul) return &Feature{
} Name: feature.Feature.Name,
NamespaceName: feature.Namespace.Name,
VersionFormat: feature.Namespace.VersionFormat,
Version: version,
} }
return f, nil
} }

@ -17,24 +17,21 @@ package v2
import ( import (
"fmt" "fmt"
"github.com/golang/protobuf/ptypes"
google_protobuf1 "github.com/golang/protobuf/ptypes/empty" google_protobuf1 "github.com/golang/protobuf/ptypes/empty"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"github.com/coreos/clair" "github.com/coreos/clair"
"github.com/coreos/clair/api/token"
pb "github.com/coreos/clair/api/v2/clairpb" pb "github.com/coreos/clair/api/v2/clairpb"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
"github.com/coreos/clair/pkg/commonerr" "github.com/coreos/clair/pkg/commonerr"
"github.com/coreos/clair/pkg/tarutil"
) )
// NotificationServer implements NotificationService interface for serving RPC. // NotificationServer implements NotificationService interface for serving RPC.
type NotificationServer struct { type NotificationServer struct {
Store database.Datastore Store database.Datastore
PaginationKey string
} }
// AncestryServer implements AncestryService interface for serving RPC. // AncestryServer implements AncestryService interface for serving RPC.
@ -46,211 +43,211 @@ type AncestryServer struct {
func (s *AncestryServer) PostAncestry(ctx context.Context, req *pb.PostAncestryRequest) (*pb.PostAncestryResponse, error) { func (s *AncestryServer) PostAncestry(ctx context.Context, req *pb.PostAncestryRequest) (*pb.PostAncestryResponse, error) {
ancestryName := req.GetAncestryName() ancestryName := req.GetAncestryName()
if ancestryName == "" { if ancestryName == "" {
return nil, status.Error(codes.InvalidArgument, "Failed to provide proper ancestry name") return nil, status.Error(codes.InvalidArgument, "ancestry name should not be empty")
} }
layers := req.GetLayers() layers := req.GetLayers()
if len(layers) == 0 { if len(layers) == 0 {
return nil, status.Error(codes.InvalidArgument, "At least one layer should be provided for an ancestry") return nil, status.Error(codes.InvalidArgument, "ancestry should have at least one layer")
}
ancestryFormat := req.GetFormat()
if ancestryFormat == "" {
return nil, status.Error(codes.InvalidArgument, "ancestry format should not be empty")
} }
var currentName, parentName, rootName string ancestryLayers := []clair.LayerRequest{}
for i, layer := range layers { for _, layer := range layers {
if layer == nil { if layer == nil {
err := status.Error(codes.InvalidArgument, "Failed to provide layer") err := status.Error(codes.InvalidArgument, "ancestry layer is invalid")
return nil, s.rollBackOnError(err, currentName, rootName) return nil, err
} }
// TODO(keyboardnerd): after altering the database to support ancestry, if layer.GetHash() == "" {
// we should use the ancestry name and index as key instead of return nil, status.Error(codes.InvalidArgument, "ancestry layer hash should not be empty")
// the amalgamation of ancestry name of index
// Hack: layer name is [ancestryName]-[index] except the tail layer,
// tail layer name is [ancestryName]
if i == len(layers)-1 {
currentName = ancestryName
} else {
currentName = fmt.Sprintf("%s-%d", ancestryName, i)
} }
// if rootName is unset, this is the first iteration over the layers and if layer.GetPath() == "" {
// the current layer is the root of the ancestry return nil, status.Error(codes.InvalidArgument, "ancestry layer path should not be empty")
if rootName == "" {
rootName = currentName
} }
err := clair.ProcessLayer(s.Store, req.GetFormat(), currentName, parentName, layer.GetPath(), layer.GetHeaders()) ancestryLayers = append(ancestryLayers, clair.LayerRequest{
if err != nil { Hash: layer.Hash,
return nil, s.rollBackOnError(err, currentName, rootName) Headers: layer.Headers,
} Path: layer.Path,
})
}
// Now that the current layer is processed, set the parentName for the err := clair.ProcessAncestry(s.Store, ancestryFormat, ancestryName, ancestryLayers)
// next iteration. if err != nil {
parentName = currentName return nil, status.Error(codes.Internal, "ancestry is failed to be processed: "+err.Error())
} }
return &pb.PostAncestryResponse{ clairStatus, err := s.getClairStatus()
EngineVersion: clair.Version, if err != nil {
}, nil return nil, status.Error(codes.Internal, err.Error())
}
return &pb.PostAncestryResponse{Status: clairStatus}, nil
} }
// GetAncestry implements retrieving an ancestry via the Clair gRPC service. func (s *AncestryServer) getClairStatus() (*pb.ClairStatus, error) {
func (s *AncestryServer) GetAncestry(ctx context.Context, req *pb.GetAncestryRequest) (*pb.GetAncestryResponse, error) { status := &pb.ClairStatus{
if req.GetAncestryName() == "" { Listers: clair.Processors.Listers,
return nil, status.Errorf(codes.InvalidArgument, "invalid get ancestry request") Detectors: clair.Processors.Detectors,
} }
// TODO(keyboardnerd): after altering the database to support ancestry, this t, firstUpdate, err := clair.GetLastUpdateTime(s.Store)
// function is iteratively querying for for r.GetIndex() th parent of the if err != nil {
// requested layer until the indexed layer is found or index is out of bound return nil, err
// this is a hack and will be replaced with one query }
ancestry, features, err := s.getAncestry(req.GetAncestryName(), req.GetWithFeatures(), req.GetWithVulnerabilities()) if firstUpdate {
if err == commonerr.ErrNotFound { return status, nil
return nil, status.Error(codes.NotFound, err.Error())
} else if err != nil {
return nil, status.Error(codes.Internal, err.Error())
} }
return &pb.GetAncestryResponse{ status.LastUpdateTime, err = ptypes.TimestampProto(t)
Ancestry: ancestry, if err != nil {
Features: features, return nil, err
}, nil }
return status, nil
} }
// GetNotification implements retrieving a notification via the Clair gRPC // GetAncestry implements retrieving an ancestry via the Clair gRPC service.
// service. func (s *AncestryServer) GetAncestry(ctx context.Context, req *pb.GetAncestryRequest) (*pb.GetAncestryResponse, error) {
func (s *NotificationServer) GetNotification(ctx context.Context, req *pb.GetNotificationRequest) (*pb.GetNotificationResponse, error) { if req.GetAncestryName() == "" {
if req.GetName() == "" { return nil, status.Errorf(codes.InvalidArgument, "ancestry name should not be empty")
return nil, status.Error(codes.InvalidArgument, "Failed to provide notification name")
} }
if req.GetLimit() <= 0 { tx, err := s.Store.Begin()
return nil, status.Error(codes.InvalidArgument, "Failed to provide page limit") if err != nil {
return nil, status.Error(codes.Internal, err.Error())
} }
defer tx.Rollback()
page := database.VulnerabilityNotificationFirstPage ancestry, _, ok, err := tx.FindAncestry(req.GetAncestryName())
pageToken := req.GetPage() if err != nil {
if pageToken != "" { return nil, status.Error(codes.Internal, err.Error())
err := token.Unmarshal(pageToken, s.PaginationKey, &page) } else if !ok {
return nil, status.Error(codes.NotFound, fmt.Sprintf("requested ancestry '%s' is not found", req.GetAncestryName()))
}
pbAncestry := pb.AncestryFromDatabaseModel(ancestry)
if req.GetWithFeatures() || req.GetWithVulnerabilities() {
ancestryWFeature, ok, err := tx.FindAncestryFeatures(ancestry.Name)
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Invalid page format %s", err.Error()) return nil, status.Error(codes.Internal, err.Error())
} }
} else {
pageTokenBytes, err := token.Marshal(page, s.PaginationKey) if !ok {
if err != nil { return nil, status.Error(codes.NotFound, fmt.Sprintf("requested ancestry '%s' is not found", req.GetAncestryName()))
return nil, status.Errorf(codes.InvalidArgument, "Failed to marshal token: %s", err.Error())
} }
pageToken = string(pageTokenBytes) pbAncestry.ScannedDetectors = ancestryWFeature.ProcessedBy.Detectors
} pbAncestry.ScannedListers = ancestryWFeature.ProcessedBy.Listers
dbNotification, nextPage, err := s.Store.GetNotification(req.GetName(), int(req.GetLimit()), page) if req.GetWithVulnerabilities() {
if err == commonerr.ErrNotFound { featureVulnerabilities, err := tx.FindAffectedNamespacedFeatures(ancestryWFeature.Features)
return nil, status.Error(codes.NotFound, err.Error()) if err != nil {
} else if err != nil { return nil, status.Error(codes.Internal, err.Error())
return nil, status.Error(codes.Internal, err.Error()) }
for _, fv := range featureVulnerabilities {
// Ensure that every feature can be found.
if !fv.Valid {
return nil, status.Error(codes.Internal, "ancestry feature is not found")
}
pbFeature := pb.NamespacedFeatureFromDatabaseModel(fv.NamespacedFeature)
for _, v := range fv.AffectedBy {
pbVuln, err := pb.VulnerabilityWithFixedInFromDatabaseModel(v)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
pbFeature.Vulnerabilities = append(pbFeature.Vulnerabilities, pbVuln)
}
pbAncestry.Features = append(pbAncestry.Features, pbFeature)
}
} else {
for _, f := range ancestryWFeature.Features {
pbAncestry.Features = append(pbAncestry.Features, pb.NamespacedFeatureFromDatabaseModel(f))
}
}
} }
notification, err := pb.NotificationFromDatabaseModel(dbNotification, int(req.GetLimit()), pageToken, nextPage, s.PaginationKey) clairStatus, err := s.getClairStatus()
if err != nil { if err != nil {
return nil, status.Error(codes.Internal, err.Error()) return nil, status.Error(codes.Internal, err.Error())
} }
return &pb.GetNotificationResponse{Notification: notification}, nil return &pb.GetAncestryResponse{
Status: clairStatus,
Ancestry: pbAncestry,
}, nil
} }
// DeleteNotification implements deleting a notification via the Clair gRPC // GetNotification implements retrieving a notification via the Clair gRPC
// service. // service.
func (s *NotificationServer) DeleteNotification(ctx context.Context, req *pb.DeleteNotificationRequest) (*google_protobuf1.Empty, error) { func (s *NotificationServer) GetNotification(ctx context.Context, req *pb.GetNotificationRequest) (*pb.GetNotificationResponse, error) {
if req.GetName() == "" { if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "Failed to provide notification name") return nil, status.Error(codes.InvalidArgument, "notification name should not be empty")
} }
err := s.Store.DeleteNotification(req.GetName()) if req.GetLimit() <= 0 {
if err == commonerr.ErrNotFound { return nil, status.Error(codes.InvalidArgument, "notification page limit should not be empty or less than 1")
return nil, status.Error(codes.NotFound, err.Error()) }
} else if err != nil {
tx, err := s.Store.Begin()
if err != nil {
return nil, status.Error(codes.Internal, err.Error()) return nil, status.Error(codes.Internal, err.Error())
} }
defer tx.Rollback()
return &google_protobuf1.Empty{}, nil dbNotification, ok, err := tx.FindVulnerabilityNotification(
} req.GetName(),
int(req.GetLimit()),
database.PageNumber(req.GetOldVulnerabilityPage()),
database.PageNumber(req.GetNewVulnerabilityPage()),
)
// rollBackOnError handles server error and rollback whole ancestry insertion if if err != nil {
// any layer failed to be inserted. return nil, status.Error(codes.Internal, err.Error())
func (s *AncestryServer) rollBackOnError(err error, currentLayerName, rootLayerName string) error {
// if the current layer failed to be inserted and it's the root layer,
// then the ancestry is not yet in the database.
if currentLayerName != rootLayerName {
errrb := s.Store.DeleteLayer(rootLayerName)
if errrb != nil {
return status.Errorf(codes.Internal, errrb.Error())
}
log.WithField("layer name", currentLayerName).Warnf("Can't process %s: roll back the ancestry", currentLayerName)
} }
if err == tarutil.ErrCouldNotExtract || if !ok {
err == tarutil.ErrExtractedFileTooBig || return nil, status.Error(codes.NotFound, fmt.Sprintf("requested notification '%s' is not found", req.GetName()))
err == clair.ErrUnsupported {
return status.Errorf(codes.InvalidArgument, "unprocessable entity %s", err.Error())
} }
if _, badreq := err.(*commonerr.ErrBadRequest); badreq { notification, err := pb.NotificationFromDatabaseModel(dbNotification)
return status.Error(codes.InvalidArgument, err.Error()) if err != nil {
return nil, status.Error(codes.Internal, err.Error())
} }
return status.Error(codes.Internal, err.Error()) return &pb.GetNotificationResponse{Notification: notification}, nil
} }
// TODO(keyboardnerd): Remove this Legacy compability code once the database is // MarkNotificationAsRead implements deleting a notification via the Clair gRPC
// revised. // service.
// getAncestry returns an ancestry from database by getting all parents of a func (s *NotificationServer) MarkNotificationAsRead(ctx context.Context, req *pb.MarkNotificationAsReadRequest) (*google_protobuf1.Empty, error) {
// layer given the layer name, and the layer's feature list if if req.GetName() == "" {
// withFeature/withVulnerability is turned on. return nil, status.Error(codes.InvalidArgument, "notification name should not be empty")
func (s *AncestryServer) getAncestry(name string, withFeature bool, withVulnerability bool) (ancestry *pb.Ancestry, features []*pb.Feature, err error) {
var (
layers = []*pb.Layer{}
layer database.Layer
)
ancestry = &pb.Ancestry{}
layer, err = s.Store.FindLayer(name, withFeature, withVulnerability)
if err != nil {
return
} }
if withFeature { tx, err := s.Store.Begin()
for _, fv := range layer.Features { if err != nil {
f, e := pb.FeatureFromDatabaseModel(fv, withVulnerability) return nil, status.Error(codes.Internal, err.Error())
if e != nil {
err = e
return
}
features = append(features, f)
}
} }
ancestry.Name = name defer tx.Rollback()
ancestry.EngineVersion = int32(layer.EngineVersion) err = tx.DeleteNotification(req.GetName())
for name != "" { if err == commonerr.ErrNotFound {
layer, err = s.Store.FindLayer(name, false, false) return nil, status.Error(codes.NotFound, "requested notification \""+req.GetName()+"\" is not found")
if err != nil { } else if err != nil {
return return nil, status.Error(codes.Internal, err.Error())
}
if layer.Parent != nil {
name = layer.Parent.Name
} else {
name = ""
}
layers = append(layers, pb.LayerFromDatabaseModel(layer))
} }
// reverse layers to make the root layer at the top if err := tx.Commit(); err != nil {
for i, j := 0, len(layers)-1; i < j; i, j = i+1, j-1 { return nil, status.Error(codes.Internal, err.Error())
layers[i], layers[j] = layers[j], layers[i]
} }
ancestry.Layers = layers return &google_protobuf1.Empty{}, nil
return
} }

@ -48,7 +48,7 @@ func handleShutdown(err error) {
var ( var (
promResponseDurationMilliseconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{ promResponseDurationMilliseconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: "clair_v2_api_response_duration_milliseconds", Name: "clair_v2_api_response_duration_milliseconds",
Help: "The duration of time it takes to receieve and write a response to an V2 API request", Help: "The duration of time it takes to receive and write a response to an V2 API request",
Buckets: prometheus.ExponentialBuckets(9.375, 2, 10), Buckets: prometheus.ExponentialBuckets(9.375, 2, 10),
}, []string{"route", "code"}) }, []string{"route", "code"})
) )
@ -57,7 +57,7 @@ func init() {
prometheus.MustRegister(promResponseDurationMilliseconds) prometheus.MustRegister(promResponseDurationMilliseconds)
} }
func newGrpcServer(paginationKey string, store database.Datastore, tlsConfig *tls.Config) *grpc.Server { func newGrpcServer(store database.Datastore, tlsConfig *tls.Config) *grpc.Server {
grpcOpts := []grpc.ServerOption{ grpcOpts := []grpc.ServerOption{
grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor), grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor), grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
@ -69,7 +69,7 @@ func newGrpcServer(paginationKey string, store database.Datastore, tlsConfig *tl
grpcServer := grpc.NewServer(grpcOpts...) grpcServer := grpc.NewServer(grpcOpts...)
pb.RegisterAncestryServiceServer(grpcServer, &AncestryServer{Store: store}) pb.RegisterAncestryServiceServer(grpcServer, &AncestryServer{Store: store})
pb.RegisterNotificationServiceServer(grpcServer, &NotificationServer{PaginationKey: paginationKey, Store: store}) pb.RegisterNotificationServiceServer(grpcServer, &NotificationServer{Store: store})
return grpcServer return grpcServer
} }
@ -98,11 +98,11 @@ func logHandler(handler http.Handler) http.Handler {
} }
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"remote addr": r.RemoteAddr, "remote addr": r.RemoteAddr,
"method": r.Method, "method": r.Method,
"request uri": r.RequestURI, "request uri": r.RequestURI,
"status": statusStr, "status": statusStr,
"elapsed time": time.Since(start), "elapsed time (ms)": float64(time.Since(start).Nanoseconds()) * 1e-6,
}).Info("Handled HTTP request") }).Info("Handled HTTP request")
}) })
} }
@ -148,7 +148,7 @@ func servePrometheus(mux *http.ServeMux) {
} }
// Run initializes grpc and grpc gateway api services on the same port // Run initializes grpc and grpc gateway api services on the same port
func Run(GrpcPort int, tlsConfig *tls.Config, PaginationKey, CertFile, KeyFile string, store database.Datastore) { func Run(GrpcPort int, tlsConfig *tls.Config, CertFile, KeyFile string, store database.Datastore) {
l, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", GrpcPort)) l, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", GrpcPort))
if err != nil { if err != nil {
log.WithError(err).Fatalf("could not bind to port %d", GrpcPort) log.WithError(err).Fatalf("could not bind to port %d", GrpcPort)
@ -175,7 +175,7 @@ func Run(GrpcPort int, tlsConfig *tls.Config, PaginationKey, CertFile, KeyFile s
apiListener = tls.NewListener(tcpMux.Match(cmux.Any()), tlsConfig) apiListener = tls.NewListener(tcpMux.Match(cmux.Any()), tlsConfig)
go func() { handleShutdown(tcpMux.Serve()) }() go func() { handleShutdown(tcpMux.Serve()) }()
grpcServer := newGrpcServer(PaginationKey, store, tlsConfig) grpcServer := newGrpcServer(store, tlsConfig)
gwmux := newGrpcGatewayServer(ctx, apiListener.Addr().String(), tlsConfig) gwmux := newGrpcGatewayServer(ctx, apiListener.Addr().String(), tlsConfig)
httpMux.Handle("/", gwmux) httpMux.Handle("/", gwmux)
@ -188,7 +188,7 @@ func Run(GrpcPort int, tlsConfig *tls.Config, PaginationKey, CertFile, KeyFile s
apiListener = tcpMux.Match(cmux.Any()) apiListener = tcpMux.Match(cmux.Any())
go func() { handleShutdown(tcpMux.Serve()) }() go func() { handleShutdown(tcpMux.Serve()) }()
grpcServer := newGrpcServer(PaginationKey, store, nil) grpcServer := newGrpcServer(store, nil)
go func() { handleShutdown(grpcServer.Serve(grpcL)) }() go func() { handleShutdown(grpcServer.Serve(grpcL)) }()
gwmux := newGrpcGatewayServer(ctx, apiListener.Addr().String(), nil) gwmux := newGrpcGatewayServer(ctx, apiListener.Addr().String(), nil)

Loading…
Cancel
Save