Skip to content

Commit

Permalink
httptransport: wire in update endpoints
Browse files Browse the repository at this point in the history
Signed-off-by: Hank Donnay <hdonnay@redhat.com>
  • Loading branch information
hdonnay committed Apr 8, 2020
1 parent 9cd6cab commit e783062
Show file tree
Hide file tree
Showing 9 changed files with 703 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Documentation/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Summary

- [About](./TODO.md)
- [Operation](./TODO.md)
- [API](./TODO.md)
- [Internal Endpoints](./api_internal.md)
- [Contribution](./TODO.md)
- [Releases](./contribution/releases.md)
11 changes: 11 additions & 0 deletions Documentation/api_internal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Internal

Internal endpoints are underneath `/api/v1/internal` and are meant for
communication between Clair microservices. If Clair is operating in combo mode,
these endpoints may not exist. Any sort of API ingress should disallow clients
to talk to these endpoints.

## Updates

The `updates/` endpoints expose information about updater operations for use by the
notifier process.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.13
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/google/go-containerregistry v0.0.0-20191206185556-eb7c14b719c6
github.com/google/uuid v1.1.1
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
github.com/klauspost/compress v1.9.4
github.com/mattn/go-sqlite3 v1.11.0 // indirect
Expand Down
3 changes: 1 addition & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,6 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/quay/alas v1.0.1 h1:MuFpGGXyZlDD7+F/hrnMZmzhS8P2bjRzX9DyGmyLA+0=
github.com/quay/alas v1.0.1/go.mod h1:pseepSrG9pwry1joG7RO/RNRFJaWqiqx9qeoomeYwEk=
github.com/quay/claircore v0.0.17 h1:qiVZ33il+HwDyCsmzWBZcHeJFQbvGKdShRlaqW+UyRk=
github.com/quay/claircore v0.0.17/go.mod h1:RgqHOIBFxq9nCgO0N/WSmMLD/7u8eohZXmpBRpFg0w8=
github.com/quay/claircore v0.0.18 h1:z2FDAGYthfPKzwmE0OE2k9DOWf3QIvfOOfDGJZ7B9Po=
github.com/quay/claircore v0.0.18/go.mod h1:gpta0pdKCIAoSYiJxMs8fRNEOdRH2r+a+f6FfaDFehQ=
github.com/quay/goval-parser v0.7.0 h1:QhJXufv2w5BUzfLJSfLz01yya9MS5SWGtyo8J/EAvkY=
Expand Down Expand Up @@ -683,6 +681,7 @@ gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652 h1:VKvJ/mQ4BgCjZUDggYFxTe0qv9jPMHsZPD4Xt91Y5H4=
gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
23 changes: 23 additions & 0 deletions httptransport/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const (
IndexAPIPath = apiRoot + "index_report"
IndexReportAPIPath = apiRoot + "index_report/"
StateAPIPath = apiRoot + "state"
internalRoot = apiRoot + "internal/"
UpdatesAPIPath = internalRoot + "updates/"
)

// Server is the primary http server
Expand Down Expand Up @@ -66,10 +68,16 @@ func New(ctx context.Context, conf config.Config, indexer indexer.Service, match
switch conf.Mode {
case config.ComboMode:
t.configureComboMode()
if err := t.configureUpdateEndpoints(); err != nil {
return nil, err
}
case config.IndexerMode:
t.configureIndexerMode()
case config.MatcherMode:
t.configureMatcherMode()
if err := t.configureUpdateEndpoints(); err != nil {
return nil, err
}
}

// attach HttpTransport to server, this works because we embed http.ServeMux
Expand Down Expand Up @@ -211,6 +219,21 @@ func (t *Server) configureMatcherMode() error {
return nil
}

func (t *Server) configureUpdateEndpoints() error {
if t.matcher == nil {
return clairerror.ErrNotInitialized{"matcher service required for update inspection endpoints"}
}

h, err := UpdateDiffHandler(t.matcher)
if err != nil {
return err
}
wh := intromw.Handler(othttp.NewHandler(h, UpdatesAPIPath, t.traceOpt), UpdatesAPIPath)
t.ServeMux.Handle(UpdatesAPIPath, othttp.WithRouteTag(UpdatesAPIPath, wh))

return nil
}

// configureWithAuth will take the current serve mux and wrap it
// in an Auth middleware handler.
//
Expand Down
75 changes: 75 additions & 0 deletions httptransport/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package httptransport

import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"path"
"testing"

"github.com/google/uuid"
"github.com/quay/claircore"
"github.com/quay/claircore/libvuln/driver"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/plugin/othttp"
)

type testMatcher struct {
scanner
differ
}

// NewTestMatcher returns a testMatcher with all functions set to non-nil stubs.
func newTestMatcher() *testMatcher {
return &testMatcher{
scanner: scanner{
scan: func(context.Context, *claircore.IndexReport) (*claircore.VulnerabilityReport, error) { return nil, nil },
},
differ: differ{
delete: func(context.Context, ...uuid.UUID) error { return nil },
latest: func(context.Context) (uuid.UUID, error) { return uuid.Nil, nil },
latestOps: func(context.Context) (map[string]uuid.UUID, error) { return nil, nil },
updateDiff: func(context.Context, uuid.UUID, uuid.UUID) (*driver.UpdateDiff, error) { return nil, nil },
},
}
}

type scanner struct {
scan func(context.Context, *claircore.IndexReport) (*claircore.VulnerabilityReport, error)
}

func (s *scanner) Scan(ctx context.Context, ir *claircore.IndexReport) (*claircore.VulnerabilityReport, error) {
return s.scan(ctx, ir)
}

// TestUpdateEndpoints registers the handlers and tests that they're registered
// at the correct endpoint.
func TestUpdateEndpoints(t *testing.T) {
m := newTestMatcher()
s := &Server{
matcher: m,
ServeMux: http.NewServeMux(),
traceOpt: othttp.WithTracer(global.TraceProvider().Tracer("clair")),
}
if err := s.configureUpdateEndpoints(); err != nil {
t.Error(err)
}

srv := httptest.NewServer(s)
defer srv.Close()
u, err := url.Parse(srv.URL)
if err != nil {
t.Error(err)
}
u.Path = path.Join(u.Path, internalRoot, "updates", "")
t.Log(u)

res, err := srv.Client().Get(u.String())
if err != nil {
t.Error(err)
}
if got, want := res.StatusCode, http.StatusOK; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
}
135 changes: 135 additions & 0 deletions httptransport/updatediffhandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package httptransport

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

"github.com/google/uuid"
"github.com/quay/claircore/pkg/jsonerr"

"github.com/quay/clair/v4/matcher"
)

type updateDiffHandler struct {
matcher.Differ
}

func (h *updateDiffHandler) getLatest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

ctx := r.Context()
var validator string
if latest, err := h.Differ.LatestUpdateOperation(ctx); err == nil {
// Using a validator is an optimization.
validator = `"` + latest.String() + `"`
if unmodified(r, validator) {
w.WriteHeader(http.StatusNotModified)
return
}
}

m, err := h.LatestUpdateOperations(ctx)
if err != nil {
res := &jsonerr.Response{
Code: "update-error",
Message: fmt.Sprintf("failed to get latest update operations: %v", err),
}
jsonerr.Error(w, res, http.StatusInternalServerError)
return
}
if validator != "" {
w.Header().Set("etag", validator)
}
defer writerError(w, &err)()
err = json.NewEncoder(w).Encode(m)
}

func (h *updateDiffHandler) deleteRef(w http.ResponseWriter, r *http.Request, ref uuid.UUID) {
if r.Method != http.MethodDelete {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

ctx := r.Context()
if err := h.DeleteUpdateOperations(ctx, ref); err != nil {
res := &jsonerr.Response{
Code: "update-error",
Message: fmt.Sprintf("failed to delete diff: %v", err),
}
jsonerr.Error(w, res, http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}

func (h *updateDiffHandler) getUpdates(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

ctx := r.Context()
var prev, cur uuid.UUID
var err error
q := r.URL.Query()
prevQ, curQ := q.Get("prev"), q.Get("cur")

cur, err = uuid.Parse(curQ)
if err != nil {
res := &jsonerr.Response{
Code: "update-error",
Message: fmt.Sprintf("malformed ref: %q", curQ),
}
jsonerr.Error(w, res, http.StatusBadRequest)
return
}
if prevQ == "" {
prev = uuid.Nil
} else {
prev, err = uuid.Parse(prevQ)
if err != nil {
res := &jsonerr.Response{
Code: "update-error",
Message: fmt.Sprintf("malformed ref: %q", prevQ),
}
jsonerr.Error(w, res, http.StatusBadRequest)
return
}
}
u, err := h.UpdateDiff(ctx, prev, cur)
if err != nil {
res := &jsonerr.Response{
Code: "update-error",
Message: fmt.Sprintf("failed to get diff: %v", err),
}
jsonerr.Error(w, res, http.StatusInternalServerError)
return
}
defer writerError(w, &err)()
err = json.NewEncoder(w).Encode(u)
}

func UpdateDiffHandler(d matcher.Differ) (http.Handler, error) {
h := &updateDiffHandler{d}
mux := http.NewServeMux()
mux.HandleFunc(path.Clean(UpdatesAPIPath), http.HandlerFunc(h.getLatest))
mux.HandleFunc(UpdatesAPIPath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == UpdatesAPIPath {
h.getLatest(w, r)
return
}
ref, err := uuid.Parse(path.Base(r.URL.Path))
if err == nil {
h.deleteRef(w, r, ref)
return
}
w.WriteHeader(http.StatusNotFound)
}))
mux.HandleFunc(path.Join(UpdatesAPIPath, "diff"), http.HandlerFunc(h.getUpdates))
return mux, nil
}

0 comments on commit e783062

Please sign in to comment.