-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
httptransport: wire in update endpoints
Signed-off-by: Hank Donnay <hdonnay@redhat.com>
- Loading branch information
Showing
9 changed files
with
703 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.