Skip to content

Commit

Permalink
Louis/sync apis (#191)
Browse files Browse the repository at this point in the history
* uniform internal api

this commit makes retreiving all update operations and the latest update
operations use the same schema

Signed-off-by: ldelossa <ldelossa@redhat.com>

* unify external api

this commit uses the same http handler pattern between libvuln and
libindex and enforces a restful path semantic

Signed-off-by: ldelossa <ldelossa@redhat.com>

* update cctool endpoints

Signed-off-by: ldelossa <ldelossa@redhat.com>
  • Loading branch information
Louis DeLosSantos committed Jun 5, 2020
1 parent a7fce3e commit 303b18b
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 101 deletions.
4 changes: 2 additions & 2 deletions cmd/cctool/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,15 @@ func Report(cmd context.Context, cfg *commonConfig, args []string) error {
if err != nil {
return err
}
cmdcfg.libindex, err = libindex.Parse("index")
cmdcfg.libindex, err = libindex.Parse("index_report")
if err != nil {
return err
}
libvuln, err := url.Parse(*libvulnRoot)
if err != nil {
return err
}
cmdcfg.libvuln, err = libvuln.Parse("scan")
cmdcfg.libvuln, err = libvuln.Parse("vulnerability_report")
if err != nil {
return err
}
Expand Down
28 changes: 8 additions & 20 deletions cmd/libvulnhttp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/rs/zerolog/log"

"github.com/quay/claircore/libvuln"
libhttp "github.com/quay/claircore/libvuln/http"
)

// Config this struct is using the goconfig library for simple flag and env var
Expand Down Expand Up @@ -48,9 +47,15 @@ func main() {
log.Fatal().Msgf("failed to create libvuln %v", err)
}

httpServ := httpServer(ctx, conf, lib)
h := libvuln.NewHandler(lib)
srv := &http.Server{
Addr: conf.HTTPListenAddr,
Handler: h,
BaseContext: func(_ net.Listener) context.Context { return ctx },
}

log.Printf("starting http server on %v", conf.HTTPListenAddr)
err = httpServ.ListenAndServe()
err = srv.ListenAndServe()
if err != nil {
log.Fatal().Msgf("failed to start http server: %v", err)
}
Expand All @@ -77,23 +82,6 @@ func logLevel(conf Config) zerolog.Level {
}
}

func httpServer(ctx context.Context, conf Config, lib *libvuln.Libvuln) *http.Server {
// create our http mux and add routes
mux := http.NewServeMux()

// create server and launch in go routine
s := &http.Server{
Addr: conf.HTTPListenAddr,
Handler: mux,
BaseContext: func(_ net.Listener) context.Context { return ctx },
}

// create handlers
mux.Handle("/scan", libhttp.VulnScan(lib))

return s
}

func confToLibvulnOpts(conf Config) *libvuln.Opts {
opts := &libvuln.Opts{
ConnString: conf.ConnString,
Expand Down
27 changes: 16 additions & 11 deletions internal/vulnstore/postgres/getupdateoperations.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ func (s *Store) GetLatestUpdateRef(ctx context.Context) (uuid.UUID, error) {
return ref, nil
}

func getLatestRefs(ctx context.Context, pool *pgxpool.Pool) (map[string]uuid.UUID, error) {
const query = `SELECT DISTINCT ON (updater) updater, ref FROM update_operation ORDER BY updater, id USING >;`
func getLatestRefs(ctx context.Context, pool *pgxpool.Pool) (map[string][]driver.UpdateOperation, error) {
const query = `SELECT DISTINCT ON (updater) updater, ref, fingerprint, date FROM update_operation ORDER BY updater, id USING >;`
log := zerolog.Ctx(ctx).With().
Str("component", "internal/vulnstore/postgres/getLatestRefs").
Logger()
Expand All @@ -41,17 +41,22 @@ func getLatestRefs(ctx context.Context, pool *pgxpool.Pool) (map[string]uuid.UUI
}
defer rows.Close()

ret := make(map[string]uuid.UUID)
var u string
var id uuid.UUID
ret := make(map[string][]driver.UpdateOperation)
for rows.Next() {
if err := rows.Scan(&u, &id); err != nil {
return nil, err
ops := []driver.UpdateOperation{}
ops = append(ops, driver.UpdateOperation{})
uo := &ops[len(ops)-1]
err := rows.Scan(
&uo.Updater,
&uo.Ref,
&uo.Fingerprint,
&uo.Date,
)
if err != nil {
rows.Close()
return nil, fmt.Errorf("failed to scan update operation for updater %q: %w", uo.Updater, err)
}
ret[u] = id
}
if err := rows.Err(); err != nil {
return nil, err
ret[uo.Updater] = ops
}
log.Debug().
Int("count", len(ret)).
Expand Down
2 changes: 1 addition & 1 deletion internal/vulnstore/postgres/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (s *Store) GetUpdateDiff(ctx context.Context, a, b uuid.UUID) (*driver.Upda
return getUpdateDiff(ctx, s.pool, a, b)
}

func (s *Store) GetLatestUpdateRefs(ctx context.Context) (map[string]uuid.UUID, error) {
func (s *Store) GetLatestUpdateRefs(ctx context.Context) (map[string][]driver.UpdateOperation, error) {
return getLatestRefs(ctx, s.pool)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/vulnstore/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type Updater interface {

// GetLatestUpdateRefs reports the latest update reference for every known
// updater.
GetLatestUpdateRefs(context.Context) (map[string]uuid.UUID, error)
GetLatestUpdateRefs(context.Context) (map[string][]driver.UpdateOperation, error)
// GetLatestUpdateRef reports the latest update reference of any known
// updater.
GetLatestUpdateRef(context.Context) (uuid.UUID, error)
Expand Down
8 changes: 4 additions & 4 deletions libindex/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ type HTTP struct {
func NewHandler(l *Libindex) *HTTP {
h := &HTTP{l: l}
m := http.NewServeMux()
m.Handle("/index", http.HandlerFunc(h.Index))
m.Handle("/index/", http.HandlerFunc(h.IndexReport))
m.Handle("/state", http.HandlerFunc(h.State))
m.Handle("/affected_manifests", http.HandlerFunc(h.AffectedManifests))
m.HandleFunc("/index_report", h.Index)
m.HandleFunc("/index_report/", h.IndexReport)
m.HandleFunc("/index_state", h.State)
m.HandleFunc("/affected_manifests", h.AffectedManifests)
h.ServeMux = m
return h
}
Expand Down
210 changes: 210 additions & 0 deletions libvuln/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package libvuln

import (
"encoding/json"
"fmt"
"net/http"
"path/filepath"
"strconv"

"github.com/google/uuid"
"github.com/quay/claircore"
"github.com/quay/claircore/libvuln/driver"
je "github.com/quay/claircore/pkg/jsonerr"
"github.com/rs/zerolog"
)

var _ http.Handler = (*HTTP)(nil)

type HTTP struct {
*http.ServeMux
l *Libvuln
}

func NewHandler(l *Libvuln) *HTTP {
h := &HTTP{l: l}
m := http.NewServeMux()
m.HandleFunc("/vulnerability_report", h.VulnerabilityReport)
m.HandleFunc("/update_operation", h.UpdateOperations)
m.HandleFunc("/update_diff", h.UpdateDiff)
h.ServeMux = m
return h
}

func (h *HTTP) UpdateDiff(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := zerolog.Ctx(ctx)
if r.Method != http.MethodGet {
resp := &je.Response{
Code: "method-not-allowed",
Message: "endpoint only allows GET",
}
je.Error(w, resp, http.StatusMethodNotAllowed)
return
}
// prev param is optional.
var prev uuid.UUID
var err error
if param, ok := r.URL.Query()["prev"]; ok {
if len(param) != 0 {
prev, err = uuid.Parse(param[0])
if err != nil {
resp := &je.Response{
Code: "bad-request",
Message: "could not parse \"prev\" query param into uuid",
}
je.Error(w, resp, http.StatusBadRequest)
return
}
}
}
// cur param is required
var cur uuid.UUID
param, ok := r.URL.Query()["cur"]
if !ok || len(param) == 0 {
resp := &je.Response{
Code: "bad-request",
Message: "cur query param is required",
}
je.Error(w, resp, http.StatusBadRequest)
return
}
if cur, err = uuid.Parse(param[0]); err != nil {
resp := &je.Response{
Code: "bad-request",
Message: "could not parse \"cur query param into uuid",
}
je.Error(w, resp, http.StatusBadRequest)
return
}

diff, err := h.l.UpdateDiff(ctx, prev, cur)
if err != nil {
resp := &je.Response{
Code: "internal server error",
Message: fmt.Sprintf("could not get update operations: %v", err),
}
je.Error(w, resp, http.StatusInternalServerError)
return
}

err = json.NewEncoder(w).Encode(&diff)
if err != nil {
// Can't change header or write a different response, because we
// already started.
log.Warn().Err(err).Msg("failed to encode response")
}
}

func (h *HTTP) UpdateOperations(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := zerolog.Ctx(ctx)
switch r.Method {
case http.MethodGet:
var latest string
if param, ok := r.URL.Query()["latest"]; ok {
if len(param) != 0 {
latest = param[0]
}
}
var uos map[string][]driver.UpdateOperation
var err error
if b, _ := strconv.ParseBool(latest); b {
uos, err = h.l.LatestUpdateOperations(ctx)
} else {
uos, err = h.l.UpdateOperations(ctx)
}
if err != nil {
resp := &je.Response{
Code: "internal server error",
Message: fmt.Sprintf("could not get update operations: %v", err),
}
je.Error(w, resp, http.StatusInternalServerError)
return
}
err = json.NewEncoder(w).Encode(&uos)
if err != nil {
// Can't change header or write a different response, because we
// already started.
log.Warn().Err(err).Msg("failed to encode response")
}
return

case http.MethodDelete:
path := r.URL.Path
id := filepath.Base(path)
uuid, err := uuid.Parse(id)
if err != nil {
resp := &je.Response{
Code: "bad-request",
Message: fmt.Sprintf("could not deserialize manifest: %v", err),
}
log.Warn().Err(err).Msg("could not deserialize manifest")
je.Error(w, resp, http.StatusBadRequest)
return
}
err = h.l.DeleteUpdateOperations(ctx, uuid)
if err != nil {
resp := &je.Response{
Code: "internal server error",
Message: fmt.Sprintf("could not get update operations: %v", err),
}
je.Error(w, resp, http.StatusInternalServerError)
return
}

default:
resp := &je.Response{
Code: "method-not-allowed",
Message: "endpoint only allows POST",
}
je.Error(w, resp, http.StatusMethodNotAllowed)
return
}
}

func (h *HTTP) VulnerabilityReport(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := zerolog.Ctx(ctx)
if r.Method != http.MethodPost {
resp := &je.Response{
Code: "method-not-allowed",
Message: "endpoint only allows POST",
}
je.Error(w, resp, http.StatusMethodNotAllowed)
return
}

// deserialize IndexReport
var sr claircore.IndexReport
err := json.NewDecoder(r.Body).Decode(&sr)
if err != nil {
resp := &je.Response{
Code: "bad-request",
Message: fmt.Sprintf("could not deserialize manifest: %v", err),
}
log.Warn().Err(err).Msg("could not deserialize manifest")
je.Error(w, resp, http.StatusBadRequest)
return
}

// call scan
vr, err := h.l.Scan(ctx, &sr)
if err != nil {
resp := &je.Response{
Code: "scan-error",
Message: fmt.Sprintf("failed to start scan: %v", err),
}
log.Warn().Err(err).Msg("failed to start scan")
je.Error(w, resp, http.StatusInternalServerError)
return
}

err = json.NewEncoder(w).Encode(vr)
if err != nil {
// Can't change header or write a different response, because we
// already started.
log.Warn().Err(err).Msg("failed to encode response")
}
return
}
Loading

0 comments on commit 303b18b

Please sign in to comment.