diff --git a/api/v1/models.go b/api/v1/models.go index e3fd25fa50..d032fb5bf6 100644 --- a/api/v1/models.go +++ b/api/v1/models.go @@ -16,6 +16,7 @@ package v1 import ( "errors" + "fmt" "github.com/coreos/clair/database" "github.com/coreos/clair/utils/types" @@ -153,16 +154,36 @@ type Feature struct { } 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"` - Changed []string `json:"Changed,omitempty"` + 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"` + Changed []string `json:"Changed,omitempty"` +} + +func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotification, limit int, page, nextPage database.VulnerabilityNotificationPageNumber) Notification { + var oldVuln *VulnerabilityWithLayers + if dbNotification.OldVulnerability != nil { + *oldVuln = VulnerabilityWithLayersFromDatabaseModel(*dbNotification.OldVulnerability) + } + + // TODO(jzelinskie): implement "changed" key + return Notification{ + Name: dbNotification.Name, + Created: fmt.Sprintf("%d", dbNotification.Created.Unix()), + Notified: fmt.Sprintf("%d", dbNotification.Notified.Unix()), + Deleted: fmt.Sprintf("%d", dbNotification.Deleted.Unix()), + Limit: limit, + Page: DBPageNumberToString(page), + NextPage: DBPageNumberToString(nextPage), + Old: oldVuln, + New: VulnerabilityWithLayersFromDatabaseModel(dbNotification.NewVulnerability), + } } type VulnerabilityWithLayers struct { @@ -170,6 +191,20 @@ type VulnerabilityWithLayers struct { LayersIntroducingVulnerability []string `json:"LayersIntroducingVulnerability,omitempty"` } +func VulnerabilityWithLayersFromDatabaseModel(dbVuln database.Vulnerability) VulnerabilityWithLayers { + vuln := VulnerabilityFromDatabaseModel(dbVuln, true) + + var layers []string + for _, layer := range dbVuln.LayersIntroducingVulnerability { + layers = append(layers, layer.Name) + } + + return VulnerabilityWithLayers{ + Vulnerability: &vuln, + LayersIntroducingVulnerability: layers, + } +} + type LayerEnvelope struct { Layer *Layer `json:"Layer,omitempty"` Error *Error `json:"Error,omitempty"` @@ -184,3 +219,20 @@ type VulnerabilityEnvelope struct { Vulnerability *Vulnerability `json:"Vulnerability,omitempty"` Error *Error `json:"Error,omitempty"` } + +type NotificationEnvelope struct { + Notification *Notification `json:"Notification,omitempty"` + Error *Error `json:"Error,omitempty"` +} + +func pageStringToDBPageNumber(pageStr string) (database.VulnerabilityNotificationPageNumber, error) { + // TODO(jzelinskie): turn pagination into an encrypted token + var old, new int + _, err := fmt.Sscanf(pageStr, "%d-%d", &old, &new) + return database.VulnerabilityNotificationPageNumber{old, new}, err +} + +func DBPageNumberToString(page database.VulnerabilityNotificationPageNumber) string { + // TODO(jzelinskie): turn pagination into an encrypted token + return fmt.Sprintf("%d-%d", page.OldVulnerability, page.NewVulnerability) +} diff --git a/api/v1/routes.go b/api/v1/routes.go index 4ac7b22db9..55bdb001b6 100644 --- a/api/v1/routes.go +++ b/api/v1/routes.go @@ -18,6 +18,7 @@ import ( "encoding/json" "io" "net/http" + "strconv" "github.com/julienschmidt/httprouter" "github.com/prometheus/client_golang/prometheus" @@ -227,9 +228,41 @@ func deleteFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx } func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int { - // ez - return 0 + query := r.URL.Query() + + limitStrs, limitExists := query["limit"] + if !limitExists { + writeResponse(w, NotificationEnvelope{Error: &Error{"must provide limit query parameter"}}) + return writeHeader(w, http.StatusBadRequest) + } + limit, err := strconv.Atoi(limitStrs[0]) + if err != nil { + writeResponse(w, NotificationEnvelope{Error: &Error{"invalid limit format: " + err.Error()}}) + return writeHeader(w, http.StatusBadRequest) + } + + page := database.VulnerabilityNotificationFirstPage + pageStrs, pageExists := query["page"] + if pageExists { + page, err = pageStringToDBPageNumber(pageStrs[0]) + if err != nil { + writeResponse(w, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}}) + return writeHeader(w, http.StatusBadRequest) + } + } + + dbNotification, nextPage, err := ctx.Store.GetNotification(p.ByName("notificationName"), limit, page) + if err != nil { + writeResponse(w, NotificationEnvelope{Error: &Error{err.Error()}}) + return writeHeader(w, http.StatusInternalServerError) + } + + notification := NotificationFromDatabaseModel(dbNotification, limit, page, nextPage) + + writeResponse(w, NotificationEnvelope{Notification: ¬ification}) + return writeHeader(w, http.StatusOK) } + func deleteNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) int { // ez return 0