move config to main / decentralize config

This puts config in its relevant location and moves functions around
loading config files into the main package.

As a side effect of removing cyclic imports for the API config, the
context library is no longer used.
pull/305/head
Jimmy Zelinskie 7 years ago
parent 889615276a
commit 6a569fd945

@ -23,29 +23,37 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/coreos/pkg/capnslog"
"github.com/tylerb/graceful" "github.com/tylerb/graceful"
"github.com/coreos/clair/api/context" "github.com/coreos/clair/database"
"github.com/coreos/clair/config"
"github.com/coreos/clair/pkg/stopper" "github.com/coreos/clair/pkg/stopper"
"github.com/coreos/pkg/capnslog"
) )
const timeoutResponse = `{"Error":{"Message":"Clair failed to respond within the configured timeout window.","Type":"Timeout"}}` const timeoutResponse = `{"Error":{"Message":"Clair failed to respond within the configured timeout window.","Type":"Timeout"}}`
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "api") var log = capnslog.NewPackageLogger("github.com/coreos/clair", "api")
func Run(config *config.APIConfig, ctx *context.RouteContext, st *stopper.Stopper) { // Config is the configuration for the API service.
type Config struct {
Port int
HealthPort int
Timeout time.Duration
PaginationKey string
CertFile, KeyFile, CAFile string
}
func Run(cfg *Config, store database.Datastore, st *stopper.Stopper) {
defer st.End() defer st.End()
// Do not run the API service if there is no config. // Do not run the API service if there is no config.
if config == nil { if cfg == nil {
log.Infof("main API service is disabled.") log.Infof("main API service is disabled.")
return return
} }
log.Infof("starting main API on port %d.", config.Port) log.Infof("starting main API on port %d.", cfg.Port)
tlsConfig, err := tlsClientConfig(config.CAFile) tlsConfig, err := tlsClientConfig(cfg.CAFile)
if err != nil { if err != nil {
log.Fatalf("could not initialize client cert authentication: %s\n", err) log.Fatalf("could not initialize client cert authentication: %s\n", err)
} }
@ -57,33 +65,33 @@ func Run(config *config.APIConfig, ctx *context.RouteContext, st *stopper.Stoppe
Timeout: 0, // Already handled by our TimeOut middleware Timeout: 0, // Already handled by our TimeOut middleware
NoSignalHandling: true, // We want to use our own Stopper NoSignalHandling: true, // We want to use our own Stopper
Server: &http.Server{ Server: &http.Server{
Addr: ":" + strconv.Itoa(config.Port), Addr: ":" + strconv.Itoa(cfg.Port),
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
Handler: http.TimeoutHandler(newAPIHandler(ctx), config.Timeout, timeoutResponse), Handler: http.TimeoutHandler(newAPIHandler(cfg, store), cfg.Timeout, timeoutResponse),
}, },
} }
listenAndServeWithStopper(srv, st, config.CertFile, config.KeyFile) listenAndServeWithStopper(srv, st, cfg.CertFile, cfg.KeyFile)
log.Info("main API stopped") log.Info("main API stopped")
} }
func RunHealth(config *config.APIConfig, ctx *context.RouteContext, st *stopper.Stopper) { func RunHealth(cfg *Config, store database.Datastore, st *stopper.Stopper) {
defer st.End() defer st.End()
// Do not run the API service if there is no config. // Do not run the API service if there is no config.
if config == nil { if cfg == nil {
log.Infof("health API service is disabled.") log.Infof("health API service is disabled.")
return return
} }
log.Infof("starting health API on port %d.", config.HealthPort) log.Infof("starting health API on port %d.", cfg.HealthPort)
srv := &graceful.Server{ srv := &graceful.Server{
Timeout: 10 * time.Second, // Interrupt health checks when stopping Timeout: 10 * time.Second, // Interrupt health checks when stopping
NoSignalHandling: true, // We want to use our own Stopper NoSignalHandling: true, // We want to use our own Stopper
Server: &http.Server{ Server: &http.Server{
Addr: ":" + strconv.Itoa(config.HealthPort), Addr: ":" + strconv.Itoa(cfg.HealthPort),
Handler: http.TimeoutHandler(newHealthHandler(ctx), config.Timeout, timeoutResponse), Handler: http.TimeoutHandler(newHealthHandler(store), cfg.Timeout, timeoutResponse),
}, },
} }

@ -1,66 +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 context
import (
"net/http"
"strconv"
"time"
"github.com/coreos/pkg/capnslog"
"github.com/julienschmidt/httprouter"
"github.com/prometheus/client_golang/prometheus"
"github.com/coreos/clair/config"
"github.com/coreos/clair/database"
)
var (
log = capnslog.NewPackageLogger("github.com/coreos/clair", "api")
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, *RouteContext) (route string, status int)
func HTTPHandler(handler Handler, ctx *RouteContext) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
start := time.Now()
route, status := handler(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.Infof("%s \"%s %s\" %s (%s)", r.RemoteAddr, r.Method, r.RequestURI, statusStr, time.Since(start))
}
}
type RouteContext struct {
Store database.Datastore
Config *config.APIConfig
}

@ -1,4 +1,4 @@
// Copyright 2015 clair authors // Copyright 2017 clair authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -20,8 +20,8 @@ import (
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/coreos/clair/api/context"
"github.com/coreos/clair/api/v1" "github.com/coreos/clair/api/v1"
"github.com/coreos/clair/database"
) )
// router is an HTTP router that forwards requests to the appropriate sub-router // router is an HTTP router that forwards requests to the appropriate sub-router
@ -31,9 +31,9 @@ type router map[string]*httprouter.Router
// Let's hope we never have more than 99 API versions. // Let's hope we never have more than 99 API versions.
const apiVersionLength = len("v99") const apiVersionLength = len("v99")
func newAPIHandler(ctx *context.RouteContext) http.Handler { func newAPIHandler(cfg *Config, store database.Datastore) http.Handler {
router := make(router) router := make(router)
router["/v1"] = v1.NewRouter(ctx) router["/v1"] = v1.NewRouter(store, cfg.PaginationKey)
return router return router
} }
@ -56,21 +56,22 @@ func (rtr router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r) http.NotFound(w, r)
} }
func newHealthHandler(ctx *context.RouteContext) http.Handler { func newHealthHandler(store database.Datastore) http.Handler {
router := httprouter.New() router := httprouter.New()
router.GET("/health", context.HTTPHandler(getHealth, ctx)) router.GET("/health", healthHandler(store))
return router return router
} }
func getHealth(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func healthHandler(store database.Datastore) httprouter.Handle {
header := w.Header() return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
header.Set("Server", "clair") header := w.Header()
header.Set("Server", "clair")
status := http.StatusInternalServerError status := http.StatusInternalServerError
if ctx.Store.Ping() { if store.Ping() {
status = http.StatusOK status = http.StatusOK
} }
w.WriteHeader(status) w.WriteHeader(status)
return "health", status }
} }

@ -16,41 +16,83 @@
package v1 package v1
import ( import (
"net/http"
"strconv"
"time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/prometheus/client_golang/prometheus"
"github.com/coreos/clair/database"
)
"github.com/coreos/clair/api/context" 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.Infof("%s \"%s %s\" %s (%s)", r.RemoteAddr, r.Method, r.RequestURI, statusStr, time.Since(start))
}
}
type context struct {
Store database.Datastore
PaginationKey string
}
// NewRouter creates an HTTP router for version 1 of the Clair API. // NewRouter creates an HTTP router for version 1 of the Clair API.
func NewRouter(ctx *context.RouteContext) *httprouter.Router { func NewRouter(store database.Datastore, paginationKey string) *httprouter.Router {
router := httprouter.New() router := httprouter.New()
ctx := &context{store, paginationKey}
// Layers // Layers
router.POST("/layers", context.HTTPHandler(postLayer, ctx)) router.POST("/layers", httpHandler(postLayer, ctx))
router.GET("/layers/:layerName", context.HTTPHandler(getLayer, ctx)) router.GET("/layers/:layerName", httpHandler(getLayer, ctx))
router.DELETE("/layers/:layerName", context.HTTPHandler(deleteLayer, ctx)) router.DELETE("/layers/:layerName", httpHandler(deleteLayer, ctx))
// Namespaces // Namespaces
router.GET("/namespaces", context.HTTPHandler(getNamespaces, ctx)) router.GET("/namespaces", httpHandler(getNamespaces, ctx))
// Vulnerabilities // Vulnerabilities
router.GET("/namespaces/:namespaceName/vulnerabilities", context.HTTPHandler(getVulnerabilities, ctx)) router.GET("/namespaces/:namespaceName/vulnerabilities", httpHandler(getVulnerabilities, ctx))
router.POST("/namespaces/:namespaceName/vulnerabilities", context.HTTPHandler(postVulnerability, ctx)) router.POST("/namespaces/:namespaceName/vulnerabilities", httpHandler(postVulnerability, ctx))
router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(getVulnerability, ctx)) router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", httpHandler(getVulnerability, ctx))
router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(putVulnerability, ctx)) router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", httpHandler(putVulnerability, ctx))
router.DELETE("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(deleteVulnerability, ctx)) router.DELETE("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", httpHandler(deleteVulnerability, ctx))
// Fixes // Fixes
router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes", context.HTTPHandler(getFixes, ctx)) router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes", httpHandler(getFixes, ctx))
router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes/:fixName", context.HTTPHandler(putFix, ctx)) router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes/:fixName", httpHandler(putFix, ctx))
router.DELETE("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes/:fixName", context.HTTPHandler(deleteFix, ctx)) router.DELETE("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes/:fixName", httpHandler(deleteFix, ctx))
// Notifications // Notifications
router.GET("/notifications/:notificationName", context.HTTPHandler(getNotification, ctx)) router.GET("/notifications/:notificationName", httpHandler(getNotification, ctx))
router.DELETE("/notifications/:notificationName", context.HTTPHandler(deleteNotification, ctx)) router.DELETE("/notifications/:notificationName", httpHandler(deleteNotification, ctx))
// Metrics // Metrics
router.GET("/metrics", context.HTTPHandler(getMetrics, ctx)) router.GET("/metrics", httpHandler(getMetrics, ctx))
return router return router
} }

@ -26,7 +26,6 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/coreos/clair" "github.com/coreos/clair"
"github.com/coreos/clair/api/context"
"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" "github.com/coreos/clair/pkg/tarutil"
@ -96,7 +95,7 @@ func writeResponse(w http.ResponseWriter, r *http.Request, status int, resp inte
} }
} }
func postLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func postLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
request := LayerEnvelope{} request := LayerEnvelope{}
err := decodeJSON(r, &request) err := decodeJSON(r, &request)
if err != nil { if err != nil {
@ -138,7 +137,7 @@ func postLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx
return postLayerRoute, http.StatusCreated return postLayerRoute, http.StatusCreated
} }
func getLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func getLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
_, withFeatures := r.URL.Query()["features"] _, withFeatures := r.URL.Query()["features"]
_, withVulnerabilities := r.URL.Query()["vulnerabilities"] _, withVulnerabilities := r.URL.Query()["vulnerabilities"]
@ -157,7 +156,7 @@ func getLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *
return getLayerRoute, http.StatusOK return getLayerRoute, http.StatusOK
} }
func deleteLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func deleteLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
err := ctx.Store.DeleteLayer(p.ByName("layerName")) err := ctx.Store.DeleteLayer(p.ByName("layerName"))
if err == commonerr.ErrNotFound { if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, LayerEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, LayerEnvelope{Error: &Error{err.Error()}})
@ -171,7 +170,7 @@ func deleteLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ct
return deleteLayerRoute, http.StatusOK return deleteLayerRoute, http.StatusOK
} }
func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
dbNamespaces, err := ctx.Store.ListNamespaces() dbNamespaces, err := ctx.Store.ListNamespaces()
if err != nil { if err != nil {
writeResponse(w, r, http.StatusInternalServerError, NamespaceEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusInternalServerError, NamespaceEnvelope{Error: &Error{err.Error()}})
@ -189,7 +188,7 @@ func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params,
return getNamespacesRoute, http.StatusOK return getNamespacesRoute, http.StatusOK
} }
func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
query := r.URL.Query() query := r.URL.Query()
limitStrs, limitExists := query["limit"] limitStrs, limitExists := query["limit"]
@ -209,7 +208,7 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par
page := 0 page := 0
pageStrs, pageExists := query["page"] pageStrs, pageExists := query["page"]
if pageExists { if pageExists {
err = tokenUnmarshal(pageStrs[0], ctx.Config.PaginationKey, &page) err = tokenUnmarshal(pageStrs[0], ctx.PaginationKey, &page)
if err != nil { if err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid page format: " + err.Error()}}) writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest return getNotificationRoute, http.StatusBadRequest
@ -239,7 +238,7 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par
var nextPageStr string var nextPageStr string
if nextPage != -1 { if nextPage != -1 {
nextPageBytes, err := tokenMarshal(nextPage, ctx.Config.PaginationKey) nextPageBytes, err := tokenMarshal(nextPage, ctx.PaginationKey)
if err != nil { if err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}}) writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest return getNotificationRoute, http.StatusBadRequest
@ -251,7 +250,7 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par
return getVulnerabilitiesRoute, http.StatusOK return getVulnerabilitiesRoute, http.StatusOK
} }
func postVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func postVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
request := VulnerabilityEnvelope{} request := VulnerabilityEnvelope{}
err := decodeJSON(r, &request) err := decodeJSON(r, &request)
if err != nil { if err != nil {
@ -286,7 +285,7 @@ func postVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Para
return postVulnerabilityRoute, http.StatusCreated return postVulnerabilityRoute, http.StatusCreated
} }
func getVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func getVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
_, withFixedIn := r.URL.Query()["fixedIn"] _, withFixedIn := r.URL.Query()["fixedIn"]
dbVuln, err := ctx.Store.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName")) dbVuln, err := ctx.Store.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
@ -304,7 +303,7 @@ func getVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Param
return getVulnerabilityRoute, http.StatusOK return getVulnerabilityRoute, http.StatusOK
} }
func putVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func putVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
request := VulnerabilityEnvelope{} request := VulnerabilityEnvelope{}
err := decodeJSON(r, &request) err := decodeJSON(r, &request)
if err != nil { if err != nil {
@ -347,7 +346,7 @@ func putVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Param
return putVulnerabilityRoute, http.StatusOK return putVulnerabilityRoute, http.StatusOK
} }
func deleteVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func deleteVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
err := ctx.Store.DeleteVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName")) err := ctx.Store.DeleteVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
if err == commonerr.ErrNotFound { if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}})
@ -361,7 +360,7 @@ func deleteVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Pa
return deleteVulnerabilityRoute, http.StatusOK return deleteVulnerabilityRoute, http.StatusOK
} }
func getFixes(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func getFixes(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
dbVuln, err := ctx.Store.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName")) dbVuln, err := ctx.Store.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
if err == commonerr.ErrNotFound { if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}})
@ -376,7 +375,7 @@ func getFixes(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *
return getFixesRoute, http.StatusOK return getFixesRoute, http.StatusOK
} }
func putFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func putFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
request := FeatureEnvelope{} request := FeatureEnvelope{}
err := decodeJSON(r, &request) err := decodeJSON(r, &request)
if err != nil { if err != nil {
@ -420,7 +419,7 @@ func putFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *co
return putFixRoute, http.StatusOK return putFixRoute, http.StatusOK
} }
func deleteFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func deleteFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
err := ctx.Store.DeleteVulnerabilityFix(p.ByName("vulnerabilityNamespace"), p.ByName("vulnerabilityName"), p.ByName("fixName")) err := ctx.Store.DeleteVulnerabilityFix(p.ByName("vulnerabilityNamespace"), p.ByName("vulnerabilityName"), p.ByName("fixName"))
if err == commonerr.ErrNotFound { if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}})
@ -434,7 +433,7 @@ func deleteFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx
return deleteFixRoute, http.StatusOK return deleteFixRoute, http.StatusOK
} }
func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
query := r.URL.Query() query := r.URL.Query()
limitStrs, limitExists := query["limit"] limitStrs, limitExists := query["limit"]
@ -452,14 +451,14 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params
page := database.VulnerabilityNotificationFirstPage page := database.VulnerabilityNotificationFirstPage
pageStrs, pageExists := query["page"] pageStrs, pageExists := query["page"]
if pageExists { if pageExists {
err := tokenUnmarshal(pageStrs[0], ctx.Config.PaginationKey, &page) err := tokenUnmarshal(pageStrs[0], ctx.PaginationKey, &page)
if err != nil { if err != nil {
writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}}) writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest return getNotificationRoute, http.StatusBadRequest
} }
pageToken = pageStrs[0] pageToken = pageStrs[0]
} else { } else {
pageTokenBytes, err := tokenMarshal(page, ctx.Config.PaginationKey) pageTokenBytes, err := tokenMarshal(page, ctx.PaginationKey)
if err != nil { if err != nil {
writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}}) writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest return getNotificationRoute, http.StatusBadRequest
@ -476,13 +475,13 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params
return getNotificationRoute, http.StatusInternalServerError return getNotificationRoute, http.StatusInternalServerError
} }
notification := NotificationFromDatabaseModel(dbNotification, limit, pageToken, nextPage, ctx.Config.PaginationKey) notification := NotificationFromDatabaseModel(dbNotification, limit, pageToken, nextPage, ctx.PaginationKey)
writeResponse(w, r, http.StatusOK, NotificationEnvelope{Notification: &notification}) writeResponse(w, r, http.StatusOK, NotificationEnvelope{Notification: &notification})
return getNotificationRoute, http.StatusOK return getNotificationRoute, http.StatusOK
} }
func deleteNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func deleteNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
err := ctx.Store.DeleteNotification(p.ByName("notificationName")) err := ctx.Store.DeleteNotification(p.ByName("notificationName"))
if err == commonerr.ErrNotFound { if err == commonerr.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, NotificationEnvelope{Error: &Error{err.Error()}}) writeResponse(w, r, http.StatusNotFound, NotificationEnvelope{Error: &Error{err.Error()}})
@ -496,7 +495,7 @@ func deleteNotification(w http.ResponseWriter, r *http.Request, p httprouter.Par
return deleteNotificationRoute, http.StatusOK return deleteNotificationRoute, http.StatusOK
} }
func getMetrics(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { func getMetrics(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context) (string, int) {
prometheus.Handler().ServeHTTP(w, r) prometheus.Handler().ServeHTTP(w, r)
return getMetricsRoute, 0 return getMetricsRoute, 0
} }

@ -1,4 +1,4 @@
// Copyright 2015 clair authors // Copyright 2017 clair authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package config package main
import ( import (
"errors" "errors"
@ -20,21 +20,19 @@ import (
"os" "os"
"time" "time"
"github.com/fernet/fernet-go"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"github.com/coreos/clair"
"github.com/coreos/clair/api"
"github.com/coreos/clair/database"
"github.com/coreos/clair/ext/notification"
"github.com/fernet/fernet-go"
) )
// ErrDatasourceNotLoaded is returned when the datasource variable in the configuration file is not loaded properly // ErrDatasourceNotLoaded is returned when the datasource variable in the
// configuration file is not loaded properly
var ErrDatasourceNotLoaded = errors.New("could not load configuration: no database source specified") var ErrDatasourceNotLoaded = errors.New("could not load configuration: no database source specified")
// RegistrableComponentConfig is a configuration block that can be used to
// determine which registrable component should be initialized and pass
// custom configuration to it.
type RegistrableComponentConfig struct {
Type string
Options map[string]interface{}
}
// File represents a YAML configuration file that namespaces all Clair // File represents a YAML configuration file that namespaces all Clair
// configuration under the top-level "clair" key. // configuration under the top-level "clair" key.
type File struct { type File struct {
@ -43,57 +41,37 @@ type File struct {
// Config is the global configuration for an instance of Clair. // Config is the global configuration for an instance of Clair.
type Config struct { type Config struct {
Database RegistrableComponentConfig Database database.RegistrableComponentConfig
Updater *UpdaterConfig Updater *clair.UpdaterConfig
Notifier *NotifierConfig Notifier *notification.Config
API *APIConfig API *api.Config
}
// UpdaterConfig is the configuration for the Updater service.
type UpdaterConfig struct {
Interval time.Duration
}
// NotifierConfig is the configuration for the Notifier service and its registered notifiers.
type NotifierConfig struct {
Attempts int
RenotifyInterval time.Duration
Params map[string]interface{} `yaml:",inline"`
}
// APIConfig is the configuration for the API service.
type APIConfig struct {
Port int
HealthPort int
Timeout time.Duration
PaginationKey string
CertFile, KeyFile, CAFile string
} }
// DefaultConfig is a configuration that can be used as a fallback value. // DefaultConfig is a configuration that can be used as a fallback value.
func DefaultConfig() Config { func DefaultConfig() Config {
return Config{ return Config{
Database: RegistrableComponentConfig{ Database: database.RegistrableComponentConfig{
Type: "pgsql", Type: "pgsql",
}, },
Updater: &UpdaterConfig{ Updater: &clair.UpdaterConfig{
Interval: 1 * time.Hour, Interval: 1 * time.Hour,
}, },
API: &APIConfig{ API: &api.Config{
Port: 6060, Port: 6060,
HealthPort: 6061, HealthPort: 6061,
Timeout: 900 * time.Second, Timeout: 900 * time.Second,
}, },
Notifier: &NotifierConfig{ Notifier: &notification.Config{
Attempts: 5, Attempts: 5,
RenotifyInterval: 2 * time.Hour, RenotifyInterval: 2 * time.Hour,
}, },
} }
} }
// Load is a shortcut to open a file, read it, and generate a Config. // LoadConfig is a shortcut to open a file, read it, and generate a Config.
//
// It supports relative and absolute paths. Given "", it returns DefaultConfig. // It supports relative and absolute paths. Given "", it returns DefaultConfig.
func Load(path string) (config *Config, err error) { func LoadConfig(path string) (config *Config, err error) {
var cfgFile File var cfgFile File
cfgFile.Clair = DefaultConfig() cfgFile.Clair = DefaultConfig()
if path == "" { if path == "" {

@ -29,8 +29,6 @@ import (
"github.com/coreos/clair" "github.com/coreos/clair"
"github.com/coreos/clair/api" "github.com/coreos/clair/api"
"github.com/coreos/clair/api/context"
"github.com/coreos/clair/config"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
"github.com/coreos/clair/pkg/stopper" "github.com/coreos/clair/pkg/stopper"
@ -88,7 +86,7 @@ func stopCPUProfiling(f *os.File) {
} }
// Boot starts Clair instance with the provided config. // Boot starts Clair instance with the provided config.
func Boot(config *config.Config) { func Boot(config *Config) {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
st := stopper.NewStopper() st := stopper.NewStopper()
@ -105,9 +103,9 @@ func Boot(config *config.Config) {
// Start API // Start API
st.Begin() st.Begin()
go api.Run(config.API, &context.RouteContext{db, config.API}, st) go api.Run(config.API, db, st)
st.Begin() st.Begin()
go api.RunHealth(config.API, &context.RouteContext{db, config.API}, st) go api.RunHealth(config.API, db, st)
// Start updater // Start updater
st.Begin() st.Begin()
@ -136,7 +134,7 @@ func main() {
} }
// Load configuration // Load configuration
config, err := config.Load(*flagConfigPath) config, err := LoadConfig(*flagConfigPath)
if err != nil { if err != nil {
log.Fatalf("failed to load configuration: %s", err) log.Fatalf("failed to load configuration: %s", err)
} }

@ -20,8 +20,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"time" "time"
"github.com/coreos/clair/config"
) )
var ( var (
@ -35,11 +33,19 @@ var (
ErrInconsistent = errors.New("database: inconsistent database") ErrInconsistent = errors.New("database: inconsistent database")
) )
// RegistrableComponentConfig is a configuration block that can be used to
// determine which registrable component should be initialized and pass custom
// configuration to it.
type RegistrableComponentConfig struct {
Type string
Options map[string]interface{}
}
var drivers = make(map[string]Driver) var drivers = make(map[string]Driver)
// Driver is a function that opens a Datastore specified by its database driver type and specific // Driver is a function that opens a Datastore specified by its database driver type and specific
// configuration. // configuration.
type Driver func(config.RegistrableComponentConfig) (Datastore, error) type Driver func(RegistrableComponentConfig) (Datastore, error)
// Register makes a Constructor available by the provided name. // Register makes a Constructor available by the provided name.
// //
@ -56,7 +62,7 @@ func Register(name string, driver Driver) {
} }
// Open opens a Datastore specified by a configuration. // Open opens a Datastore specified by a configuration.
func Open(cfg config.RegistrableComponentConfig) (Datastore, error) { func Open(cfg RegistrableComponentConfig) (Datastore, error) {
driver, ok := drivers[cfg.Type] driver, ok := drivers[cfg.Type]
if !ok { if !ok {
return nil, fmt.Errorf("database: unknown Driver %q (forgotten configuration or import?)", cfg.Type) return nil, fmt.Errorf("database: unknown Driver %q (forgotten configuration or import?)", cfg.Type)

@ -31,7 +31,6 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/remind101/migrate" "github.com/remind101/migrate"
"github.com/coreos/clair/config"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
"github.com/coreos/clair/database/pgsql/migrations" "github.com/coreos/clair/database/pgsql/migrations"
"github.com/coreos/clair/pkg/commonerr" "github.com/coreos/clair/pkg/commonerr"
@ -114,11 +113,13 @@ type Config struct {
FixturePath string FixturePath string
} }
// openDatabase opens a PostgresSQL-backed Datastore using the given configuration. // openDatabase opens a PostgresSQL-backed Datastore using the given
// It immediately every necessary migrations. If ManageDatabaseLifecycle is specified, // configuration.
// the database will be created first. If FixturePath is specified, every SQL queries that are //
// present insides will be executed. // It immediately runs all necessary migrations. If ManageDatabaseLifecycle is
func openDatabase(registrableComponentConfig config.RegistrableComponentConfig) (database.Datastore, error) { // specified, the database will be created first. If FixturePath is specified,
// every SQL queries that are present insides will be executed.
func openDatabase(registrableComponentConfig database.RegistrableComponentConfig) (database.Datastore, error) {
var pg pgSQL var pg pgSQL
var err error var err error

@ -21,8 +21,9 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/coreos/clair/config"
"github.com/pborman/uuid" "github.com/pborman/uuid"
"github.com/coreos/clair/database"
) )
func openDatabaseForTest(testName string, loadFixture bool) (*pgSQL, error) { func openDatabaseForTest(testName string, loadFixture bool) (*pgSQL, error) {
@ -34,7 +35,7 @@ func openDatabaseForTest(testName string, loadFixture bool) (*pgSQL, error) {
return datastore, nil return datastore, nil
} }
func generateTestConfig(testName string, loadFixture bool) config.RegistrableComponentConfig { func generateTestConfig(testName string, loadFixture bool) database.RegistrableComponentConfig {
dbName := "test_" + strings.ToLower(testName) + "_" + strings.Replace(uuid.New(), "-", "_", -1) dbName := "test_" + strings.ToLower(testName) + "_" + strings.Replace(uuid.New(), "-", "_", -1)
var fixturePath string var fixturePath string
@ -48,7 +49,7 @@ func generateTestConfig(testName string, loadFixture bool) config.RegistrableCom
source = fmt.Sprintf(sourceEnv, dbName) source = fmt.Sprintf(sourceEnv, dbName)
} }
return config.RegistrableComponentConfig{ return database.RegistrableComponentConfig{
Options: map[string]interface{}{ Options: map[string]interface{}{
"source": source, "source": source,
"cachesize": 0, "cachesize": 0,

@ -22,10 +22,10 @@ package notification
import ( import (
"sync" "sync"
"time"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
"github.com/coreos/clair/config"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
) )
@ -36,11 +36,19 @@ var (
senders = make(map[string]Sender) senders = make(map[string]Sender)
) )
// Config is the configuration for the Notifier service and its registered
// notifiers.
type Config struct {
Attempts int
RenotifyInterval time.Duration
Params map[string]interface{} `yaml:",inline"`
}
// Sender represents anything that can transmit notifications. // Sender represents anything that can transmit notifications.
type Sender interface { type Sender interface {
// Configure attempts to initialize the notifier with the provided configuration. // Configure attempts to initialize the notifier with the provided configuration.
// It returns whether the notifier is enabled or not. // It returns whether the notifier is enabled or not.
Configure(*config.NotifierConfig) (bool, error) Configure(*Config) (bool, error)
// Send informs the existence of the specified notification. // Send informs the existence of the specified notification.
Send(notification database.VulnerabilityNotification) error Send(notification database.VulnerabilityNotification) error

@ -29,7 +29,6 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"github.com/coreos/clair/config"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
"github.com/coreos/clair/ext/notification" "github.com/coreos/clair/ext/notification"
) )
@ -55,7 +54,7 @@ func init() {
notification.RegisterSender("webhook", &sender{}) notification.RegisterSender("webhook", &sender{})
} }
func (s *sender) Configure(config *config.NotifierConfig) (bool, error) { func (s *sender) Configure(config *notification.Config) (bool, error) {
// Get configuration // Get configuration
var httpConfig Config var httpConfig Config
if config == nil { if config == nil {

@ -21,7 +21,6 @@ import (
"github.com/pborman/uuid" "github.com/pborman/uuid"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/coreos/clair/config"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
"github.com/coreos/clair/ext/notification" "github.com/coreos/clair/ext/notification"
"github.com/coreos/clair/pkg/commonerr" "github.com/coreos/clair/pkg/commonerr"
@ -54,7 +53,7 @@ func init() {
// RunNotifier begins a process that checks for new notifications that should // RunNotifier begins a process that checks for new notifications that should
// be sent out to third parties. // be sent out to third parties.
func RunNotifier(config *config.NotifierConfig, datastore database.Datastore, stopper *stopper.Stopper) { func RunNotifier(config *notification.Config, datastore database.Datastore, stopper *stopper.Stopper) {
defer stopper.End() defer stopper.End()
// Configure registered notifiers. // Configure registered notifiers.

@ -24,7 +24,6 @@ import (
"github.com/pborman/uuid" "github.com/pborman/uuid"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/coreos/clair/config"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
"github.com/coreos/clair/ext/vulnmdsrc" "github.com/coreos/clair/ext/vulnmdsrc"
"github.com/coreos/clair/ext/vulnsrc" "github.com/coreos/clair/ext/vulnsrc"
@ -63,9 +62,14 @@ func init() {
prometheus.MustRegister(promUpdaterNotesTotal) prometheus.MustRegister(promUpdaterNotesTotal)
} }
// UpdaterConfig is the configuration for the Updater service.
type UpdaterConfig struct {
Interval time.Duration
}
// RunUpdater begins a process that updates the vulnerability database at // RunUpdater begins a process that updates the vulnerability database at
// regular intervals. // regular intervals.
func RunUpdater(config *config.UpdaterConfig, datastore database.Datastore, st *stopper.Stopper) { func RunUpdater(config *UpdaterConfig, datastore database.Datastore, st *stopper.Stopper) {
defer st.End() defer st.End()
// Do not run the updater if there is no config or if the interval is 0. // Do not run the updater if there is no config or if the interval is 0.

@ -78,9 +78,9 @@ func TestProcessWithDistUpgrade(t *testing.T) {
// wheezy.tar: FROM debian:wheezy // wheezy.tar: FROM debian:wheezy
// jessie.tar: RUN sed -i "s/precise/trusty/" /etc/apt/sources.list && apt-get update && // jessie.tar: RUN sed -i "s/precise/trusty/" /etc/apt/sources.list && apt-get update &&
// apt-get -y dist-upgrade // apt-get -y dist-upgrade
assert.Nil(t, Process(datastore, "Docker", "blank", "", testDataPath+"blank.tar.gz", nil)) assert.Nil(t, ProcessLayer(datastore, "Docker", "blank", "", testDataPath+"blank.tar.gz", nil))
assert.Nil(t, Process(datastore, "Docker", "wheezy", "blank", testDataPath+"wheezy.tar.gz", nil)) assert.Nil(t, ProcessLayer(datastore, "Docker", "wheezy", "blank", testDataPath+"wheezy.tar.gz", nil))
assert.Nil(t, Process(datastore, "Docker", "jessie", "wheezy", testDataPath+"jessie.tar.gz", nil)) assert.Nil(t, ProcessLayer(datastore, "Docker", "jessie", "wheezy", testDataPath+"jessie.tar.gz", nil))
// Ensure that the 'wheezy' layer has the expected namespace and features. // Ensure that the 'wheezy' layer has the expected namespace and features.
wheezy, ok := datastore.layers["wheezy"] wheezy, ok := datastore.layers["wheezy"]

Loading…
Cancel
Save