parent
6c9a131b09
commit
a378cb070c
@ -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: ¬ification})
|
|
||||||
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
|
|
||||||
}
|
|
Loading…
Reference in new issue