api: add initial work on the new API
This commit is contained in:
parent
b3ddfbc353
commit
822ac7ab4c
44
api/api.go
44
api/api.go
@ -12,8 +12,6 @@
|
|||||||
// 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 api provides a RESTful HTTP API, enabling external apps to interact
|
|
||||||
// with clair.
|
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -25,37 +23,17 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/pkg/capnslog"
|
"github.com/prometheus/common/log"
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
"github.com/tylerb/graceful"
|
"github.com/tylerb/graceful"
|
||||||
|
|
||||||
|
"github.com/coreos/clair/api/context"
|
||||||
"github.com/coreos/clair/config"
|
"github.com/coreos/clair/config"
|
||||||
"github.com/coreos/clair/database"
|
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "api")
|
const timeoutResponse = `{"Error":{"Message":"Clair failed to respond within the configured timeout window.","Type":"Timeout"}}`
|
||||||
|
|
||||||
// Env stores the environment used by the API.
|
func Run(config *config.APIConfig, ctx *context.RouteContext, st *utils.Stopper) {
|
||||||
type Env struct {
|
|
||||||
Datastore database.Datastore
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle adds a fourth parameter to httprouter.Handle: a pointer to *Env,
|
|
||||||
// allowing us to pass our environment to the handler.
|
|
||||||
type Handle func(http.ResponseWriter, *http.Request, httprouter.Params, *Env)
|
|
||||||
|
|
||||||
// WrapHandle encloses a Handle into a httprouter.Handle to make it usable by
|
|
||||||
// httprouter.
|
|
||||||
func WrapHandle(fn Handle, e *Env) httprouter.Handle {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
||||||
fn(w, r, p, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run launches the main API, which exposes every possible interactions
|
|
||||||
// with clair.
|
|
||||||
func Run(config *config.APIConfig, env *Env, st *utils.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.
|
||||||
@ -79,18 +57,16 @@ func Run(config *config.APIConfig, env *Env, st *utils.Stopper) {
|
|||||||
Server: &http.Server{
|
Server: &http.Server{
|
||||||
Addr: ":" + strconv.Itoa(config.Port),
|
Addr: ":" + strconv.Itoa(config.Port),
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
Handler: NewVersionRouter(config.Timeout, env),
|
Handler: http.TimeoutHandler(newAPIHandler(ctx), config.Timeout, timeoutResponse),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
listenAndServeWithStopper(srv, st, config.CertFile, config.KeyFile)
|
listenAndServeWithStopper(srv, st, config.CertFile, config.KeyFile)
|
||||||
|
|
||||||
log.Info("main API stopped")
|
log.Info("main API stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunHealth launches the Health API, which only exposes a method to fetch
|
func RunHealth(config *config.APIConfig, ctx *context.RouteContext, st *utils.Stopper) {
|
||||||
// Clair's health without any security or authentication mechanism.
|
|
||||||
func RunHealth(config *config.APIConfig, env *Env, st *utils.Stopper) {
|
|
||||||
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 config == nil {
|
||||||
log.Infof("health API service is disabled.")
|
log.Infof("health API service is disabled.")
|
||||||
@ -103,10 +79,12 @@ func RunHealth(config *config.APIConfig, env *Env, st *utils.Stopper) {
|
|||||||
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(config.HealthPort),
|
||||||
Handler: NewHealthRouter(env),
|
Handler: http.TimeoutHandler(newHealthHandler(ctx), config.Timeout, timeoutResponse),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
listenAndServeWithStopper(srv, st, "", "")
|
listenAndServeWithStopper(srv, st, "", "")
|
||||||
|
|
||||||
log.Info("health API stopped")
|
log.Info("health API stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
45
api/context/context.go
Normal file
45
api/context/context.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// 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 context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/coreos/pkg/capnslog"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
|
||||||
|
"github.com/coreos/clair/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "api")
|
||||||
|
|
||||||
|
type Handler func(http.ResponseWriter, *http.Request, httprouter.Params, *RouteContext) int
|
||||||
|
|
||||||
|
func HTTPHandler(handler Handler, ctx *RouteContext) httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
status := handler(w, r, p, ctx)
|
||||||
|
statusStr := fmt.Sprintf("%d", status)
|
||||||
|
if status == 0 {
|
||||||
|
statusStr = "???"
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("%s %s %s %s", statusStr, r.Method, r.RequestURI, r.RemoteAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouteContext struct {
|
||||||
|
Store database.Datastore
|
||||||
|
}
|
102
api/handlers.go
102
api/handlers.go
@ -1,102 +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 api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
|
||||||
httputils "github.com/coreos/clair/utils/http"
|
|
||||||
"github.com/coreos/clair/worker"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Version is an integer representing the API version.
|
|
||||||
const Version = 1
|
|
||||||
|
|
||||||
// POSTLayersParameters represents the expected parameters for POSTLayers.
|
|
||||||
type POSTLayersParameters struct {
|
|
||||||
Name, Path, ParentName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GETVersions returns API and Engine versions.
|
|
||||||
func GETVersions(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ *Env) {
|
|
||||||
httputils.WriteHTTP(w, http.StatusOK, struct {
|
|
||||||
APIVersion string
|
|
||||||
EngineVersion string
|
|
||||||
}{
|
|
||||||
APIVersion: strconv.Itoa(Version),
|
|
||||||
EngineVersion: strconv.Itoa(worker.Version),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GETHealth sums up the health of all the registered services.
|
|
||||||
func GETHealth(w http.ResponseWriter, r *http.Request, _ httprouter.Params, e *Env) {
|
|
||||||
// globalHealth, statuses := health.Healthcheck(e.Datastore)
|
|
||||||
//
|
|
||||||
// httpStatus := http.StatusOK
|
|
||||||
// if !globalHealth {
|
|
||||||
// httpStatus = http.StatusServiceUnavailable
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// httputils.WriteHTTP(w, httpStatus, statuses)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// POSTLayers analyzes a layer and returns the engine version that has been used
|
|
||||||
// for the analysis.
|
|
||||||
func POSTLayers(w http.ResponseWriter, r *http.Request, _ httprouter.Params, e *Env) {
|
|
||||||
var parameters POSTLayersParameters
|
|
||||||
if s, err := httputils.ParseHTTPBody(r, ¶meters); err != nil {
|
|
||||||
httputils.WriteHTTPError(w, s, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process data.
|
|
||||||
if err := worker.Process(e.Datastore, parameters.Name, parameters.ParentName, parameters.Path); err != nil {
|
|
||||||
httputils.WriteHTTPError(w, 0, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get engine version and return.
|
|
||||||
httputils.WriteHTTP(w, http.StatusCreated, struct{ Version string }{Version: strconv.Itoa(worker.Version)})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETELayers deletes the specified layer and any child layers that are
|
|
||||||
// dependent on the specified layer.
|
|
||||||
func DELETELayers(w http.ResponseWriter, r *http.Request, p httprouter.Params, e *Env) {
|
|
||||||
if err := e.Datastore.DeleteLayer(p.ByName("name")); err != nil {
|
|
||||||
httputils.WriteHTTPError(w, 0, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
httputils.WriteHTTP(w, http.StatusNoContent, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GETLayers returns informations about an existing layer, optionally with its features
|
|
||||||
// and vulnerabilities.
|
|
||||||
func GETLayers(w http.ResponseWriter, r *http.Request, p httprouter.Params, e *Env) {
|
|
||||||
_, withFeatures := r.URL.Query()["withFeatures"]
|
|
||||||
_, withVulnerabilities := r.URL.Query()["withVulnerabilities"]
|
|
||||||
|
|
||||||
layer, err := e.Datastore.FindLayer(p.ByName("name"), withFeatures, withVulnerabilities)
|
|
||||||
if err != nil {
|
|
||||||
httputils.WriteHTTPError(w, 0, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
httputils.WriteHTTP(w, http.StatusOK, struct{ Layer database.Layer }{Layer: layer})
|
|
||||||
}
|
|
@ -1,69 +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 wrappers contains httprouter.Handle wrappers that are used in the API.
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
)
|
|
||||||
|
|
||||||
type logWriter struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
status int
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lw *logWriter) Header() http.Header {
|
|
||||||
return lw.ResponseWriter.Header()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lw *logWriter) Write(b []byte) (int, error) {
|
|
||||||
if !lw.Written() {
|
|
||||||
lw.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
size, err := lw.ResponseWriter.Write(b)
|
|
||||||
lw.size += size
|
|
||||||
return size, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lw *logWriter) WriteHeader(s int) {
|
|
||||||
lw.status = s
|
|
||||||
lw.ResponseWriter.WriteHeader(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lw *logWriter) Size() int {
|
|
||||||
return lw.size
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lw *logWriter) Written() bool {
|
|
||||||
return lw.status != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lw *logWriter) Status() int {
|
|
||||||
return lw.status
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logger wraps an Handler and logs the API call
|
|
||||||
func Logger(fn httprouter.Handle) httprouter.Handle {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
||||||
lw := &logWriter{ResponseWriter: w}
|
|
||||||
start := time.Now()
|
|
||||||
fn(lw, r, p)
|
|
||||||
log.Infof("%d %s %s (%s)", lw.Status(), r.Method, r.RequestURI, time.Since(start))
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,75 +17,50 @@ package api
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
|
|
||||||
|
"github.com/coreos/clair/api/context"
|
||||||
|
"github.com/coreos/clair/api/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VersionRouter is an HTTP router that forwards requests to the appropriate
|
// router is an HTTP router that forwards requests to the appropriate sub-router
|
||||||
// router depending on the API version specified in the requested URI.
|
// depending on the API version specified in the request URI.
|
||||||
type VersionRouter map[string]*httprouter.Router
|
type router map[string]*httprouter.Router
|
||||||
|
|
||||||
// NewVersionRouter instantiates a VersionRouter and every sub-routers that are
|
// Let's hope we never have more than 99 API versions.
|
||||||
// necessary to handle supported API versions.
|
const apiVersionLength = len("v99")
|
||||||
func NewVersionRouter(to time.Duration, env *Env) *VersionRouter {
|
|
||||||
return &VersionRouter{
|
func newAPIHandler(ctx *context.RouteContext) http.Handler {
|
||||||
"/v1": NewRouterV1(to, env),
|
router := make(router)
|
||||||
}
|
router["v1"] = v1.NewRouter(ctx)
|
||||||
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP forwards requests to the appropriate router depending on the API
|
func (rtr router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
// version specified in the requested URI and remove the version information
|
|
||||||
// from the request URL.Path, without modifying the request uRequestURI.
|
|
||||||
func (vs VersionRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
urlStr := r.URL.String()
|
urlStr := r.URL.String()
|
||||||
var version string
|
var version string
|
||||||
if len(urlStr) >= 3 {
|
if len(urlStr) >= apiVersionLength {
|
||||||
version = urlStr[:3]
|
version = urlStr[:apiVersionLength]
|
||||||
}
|
}
|
||||||
if router, _ := vs[version]; router != nil {
|
|
||||||
|
if router, _ := rtr[version]; router != nil {
|
||||||
// Remove the version number from the request path to let the router do its
|
// Remove the version number from the request path to let the router do its
|
||||||
// job but do not update the RequestURI
|
// job but do not update the RequestURI
|
||||||
r.URL.Path = strings.Replace(r.URL.Path, version, "", 1)
|
r.URL.Path = strings.Replace(r.URL.Path, version, "", 1)
|
||||||
router.ServeHTTP(w, r)
|
router.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRouterV1 creates a new router for the API (Version 1)
|
func newHealthHandler(ctx *context.RouteContext) http.Handler {
|
||||||
func NewRouterV1(to time.Duration, env *Env) *httprouter.Router {
|
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
|
router.GET("/health", context.HTTPHandler(getHealth, ctx))
|
||||||
// Create a wrapper that will wrap a Handle into a httprouter.Handle and that adds
|
|
||||||
// logging and time-out capabilities.
|
|
||||||
wrap := func(fn Handle, e *Env) httprouter.Handle {
|
|
||||||
return Logger(TimeOut(to, WrapHandle(fn, e)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// General
|
|
||||||
router.GET("/versions", wrap(GETVersions, env))
|
|
||||||
router.GET("/health", wrap(GETHealth, env))
|
|
||||||
|
|
||||||
// Layers
|
|
||||||
router.POST("/layers", wrap(POSTLayers, env))
|
|
||||||
router.DELETE("/layers/:name", wrap(DELETELayers, env))
|
|
||||||
router.GET("/layers/:name", wrap(GETLayers, env))
|
|
||||||
|
|
||||||
// Vulnerabilities
|
|
||||||
// router.POST("/vulnerabilities", wrap(logic.POSTVulnerabilities))
|
|
||||||
// router.PUT("/vulnerabilities/:id", wrap(logic.PUTVulnerabilities))
|
|
||||||
// router.GET("/vulnerabilities/:id", wrap(logic.GETVulnerabilities))
|
|
||||||
// router.DELETE("/vulnerabilities/:id", wrap(logic.DELVulnerabilities))
|
|
||||||
// router.GET("/vulnerabilities/:id/introducing-layers", wrap(logic.GETVulnerabilitiesIntroducingLayers))
|
|
||||||
// router.POST("/vulnerabilities/:id/affected-layers", wrap(logic.POSTVulnerabilitiesAffectedLayers))
|
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHealthRouter creates a new router that only serve the Health function on /
|
func getHealth(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
func NewHealthRouter(env *Env) *httprouter.Router {
|
return 0
|
||||||
router := httprouter.New()
|
|
||||||
router.GET("/", WrapHandle(GETHealth, env))
|
|
||||||
return router
|
|
||||||
}
|
}
|
||||||
|
101
api/timeout.go
101
api/timeout.go
@ -1,101 +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 api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
|
|
||||||
httputils "github.com/coreos/clair/utils/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrHandlerTimeout is returned on ResponseWriter Write calls
|
|
||||||
// in handlers which have timed out.
|
|
||||||
var ErrHandlerTimeout = errors.New("http: Handler timeout")
|
|
||||||
|
|
||||||
type timeoutWriter struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
timedOut bool
|
|
||||||
wroteHeader bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tw *timeoutWriter) Header() http.Header {
|
|
||||||
return tw.ResponseWriter.Header()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tw *timeoutWriter) Write(p []byte) (int, error) {
|
|
||||||
tw.mu.Lock()
|
|
||||||
defer tw.mu.Unlock()
|
|
||||||
tw.wroteHeader = true // implicitly at least
|
|
||||||
if tw.timedOut {
|
|
||||||
return 0, ErrHandlerTimeout
|
|
||||||
}
|
|
||||||
return tw.ResponseWriter.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tw *timeoutWriter) WriteHeader(status int) {
|
|
||||||
tw.mu.Lock()
|
|
||||||
defer tw.mu.Unlock()
|
|
||||||
if tw.timedOut || tw.wroteHeader {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tw.wroteHeader = true
|
|
||||||
tw.ResponseWriter.WriteHeader(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeOut wraps an Handler and ensure that a response is given under
|
|
||||||
// the specified duration.
|
|
||||||
//
|
|
||||||
// If the handler takes longer than the time limit, the wrapper responds with
|
|
||||||
// a Service Unavailable error, an error message and the handler response which
|
|
||||||
// may come later is ignored.
|
|
||||||
//
|
|
||||||
// After a timeout, any write the handler to its ResponseWriter will return
|
|
||||||
// ErrHandlerTimeout.
|
|
||||||
//
|
|
||||||
// If the duration is 0, the wrapper does nothing.
|
|
||||||
func TimeOut(d time.Duration, fn httprouter.Handle) httprouter.Handle {
|
|
||||||
if d == 0 {
|
|
||||||
return fn
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
||||||
done := make(chan bool)
|
|
||||||
tw := &timeoutWriter{ResponseWriter: w}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
fn(tw, r, p)
|
|
||||||
done <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
case <-time.After(d):
|
|
||||||
tw.mu.Lock()
|
|
||||||
defer tw.mu.Unlock()
|
|
||||||
if !tw.wroteHeader {
|
|
||||||
httputils.WriteHTTPError(tw.ResponseWriter, http.StatusServiceUnavailable, ErrHandlerTimeout)
|
|
||||||
}
|
|
||||||
tw.timedOut = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
62
api/v1/models.go
Normal file
62
api/v1/models.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Error string
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Layer struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
Parent string
|
||||||
|
IndexedByVersion int
|
||||||
|
Features []Feature
|
||||||
|
}
|
||||||
|
|
||||||
|
type Vulnerability struct {
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
Description string
|
||||||
|
Severity string
|
||||||
|
FixedBy string
|
||||||
|
FixedIn []Feature
|
||||||
|
}
|
||||||
|
|
||||||
|
type Feature struct {
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
Version string
|
||||||
|
Vulnerabilities []Vulnerability
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notification struct {
|
||||||
|
Name string
|
||||||
|
Created string
|
||||||
|
Notified string
|
||||||
|
Deleted string
|
||||||
|
Limit int
|
||||||
|
Page string
|
||||||
|
NextPage string
|
||||||
|
Old VulnerabilityWithLayers
|
||||||
|
New VulnerabilityWithLayers
|
||||||
|
Changed []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type VulnerabilityWithLayers struct {
|
||||||
|
Vulnerability Vulnerability
|
||||||
|
LayersIntroducingVulnerability []string
|
||||||
|
}
|
56
api/v1/router.go
Normal file
56
api/v1/router.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// 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 (
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
|
||||||
|
"github.com/coreos/clair/api/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRouter creates an HTTP router for version 1 of the Clair API.
|
||||||
|
func NewRouter(ctx *context.RouteContext) *httprouter.Router {
|
||||||
|
router := httprouter.New()
|
||||||
|
|
||||||
|
// Layers
|
||||||
|
router.POST("/layers", context.HTTPHandler(postLayer, ctx))
|
||||||
|
router.GET("/layers/:layerName", context.HTTPHandler(getLayer, ctx))
|
||||||
|
router.DELETE("/layers/:layerName", context.HTTPHandler(deleteLayer, ctx))
|
||||||
|
|
||||||
|
// Namespaces
|
||||||
|
router.GET("/namespaces", context.HTTPHandler(getNamespaces, ctx))
|
||||||
|
|
||||||
|
// Vulnerabilities
|
||||||
|
router.POST("/namespaces/:namespaceName/vulnerabilities", context.HTTPHandler(postVulnerability, ctx))
|
||||||
|
router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(getVulnerability, ctx))
|
||||||
|
router.PATCH("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(patchVulnerability, ctx))
|
||||||
|
router.DELETE("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(deleteVulnerability, ctx))
|
||||||
|
|
||||||
|
// Fixes
|
||||||
|
router.POST("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes", context.HTTPHandler(postFix, ctx))
|
||||||
|
router.GET("/namespaces/:namespaceName/vulnerabilities/:vulernabilityName/fixes", context.HTTPHandler(getFixes, ctx))
|
||||||
|
router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes/:fixName", context.HTTPHandler(putFix, ctx))
|
||||||
|
router.DELETE("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes/:fixName", context.HTTPHandler(deleteFix, ctx))
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
router.GET("/notifications/:notificationName", context.HTTPHandler(getNotification, ctx))
|
||||||
|
router.DELETE("/notifications/:notificationName", context.HTTPHandler(deleteNotification, ctx))
|
||||||
|
|
||||||
|
// Metrics
|
||||||
|
router.GET("/metrics", context.HTTPHandler(getMetrics, ctx))
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
123
api/v1/routes.go
Normal file
123
api/v1/routes.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
"github.com/coreos/clair/api/context"
|
||||||
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
|
"github.com/coreos/clair/worker"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxBodySize restricts client requests to 1MiB.
|
||||||
|
const maxBodySize int64 = 1048576
|
||||||
|
|
||||||
|
func decodeJSON(r *http.Request, v interface{}) error {
|
||||||
|
defer r.Body.Close()
|
||||||
|
return json.NewDecoder(io.LimitReader(r.Body, maxBodySize)).Decode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeError(w io.Writer, err error, errType string) {
|
||||||
|
err = json.NewEncoder(w).Encode(ErrorResponse{Error{err.Error(), errType}})
|
||||||
|
if err != nil {
|
||||||
|
panic("v1: failed to marshal error response: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func postLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
request := LayerRequest{}
|
||||||
|
err := decodeJSON(r, &request)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
writeError(w, err, "BadRequest")
|
||||||
|
return http.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
err = worker.Process(ctx.Store, request.Layer.Name, request.Layer.ParentName, request.Layer.Path, request.Layer.Format)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*cerrors.ErrBadRequest); ok {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
writeError(w, err, "BadRequest")
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
writeError(w, err, "InternalServerError")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
return http.StatusCreated
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
// ez
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func deleteLayer(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
// ez
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func postVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
// ez
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func getVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
// ez
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func patchVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
// ez
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func deleteVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
// ez
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func postFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func getFixes(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func putFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func deleteFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
// ez
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func deleteNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
// ez
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMetrics(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int {
|
||||||
|
prometheus.Handler().ServeHTTP(w, r)
|
||||||
|
return 0
|
||||||
|
}
|
5
clair.go
5
clair.go
@ -23,6 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/config"
|
||||||
"github.com/coreos/clair/database/pgsql"
|
"github.com/coreos/clair/database/pgsql"
|
||||||
"github.com/coreos/clair/notifier"
|
"github.com/coreos/clair/notifier"
|
||||||
@ -52,9 +53,9 @@ func Boot(config *config.Config) {
|
|||||||
|
|
||||||
// Start API
|
// Start API
|
||||||
st.Begin()
|
st.Begin()
|
||||||
go api.Run(config.API, &api.Env{Datastore: db}, st)
|
go api.Run(config.API, &context.RouteContext{db}, st)
|
||||||
st.Begin()
|
st.Begin()
|
||||||
go api.RunHealth(config.API, &api.Env{Datastore: db}, st)
|
go api.RunHealth(config.API, &context.RouteContext{db}, st)
|
||||||
|
|
||||||
// Start updater
|
// Start updater
|
||||||
st.Begin()
|
st.Begin()
|
||||||
|
Loading…
Reference in New Issue
Block a user