Skip to content

Commit

Permalink
httptransport: debug log calls to apiError
Browse files Browse the repository at this point in the history
This will be a chatty one in a misbehaving environment, watch out.

Signed-off-by: Hank Donnay <hdonnay@redhat.com>
  • Loading branch information
hdonnay committed Feb 8, 2023
1 parent 896b2df commit 7241796
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 64 deletions.
5 changes: 3 additions & 2 deletions httptransport/concurrentlimit.go
Expand Up @@ -38,14 +38,15 @@ func (l *limitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if sem != nil {
if !sem.TryAcquire(1) {
concurrentLimitedCounter.WithLabelValues(endpt, r.Method).Add(1)
zlog.Info(r.Context()).
ctx := r.Context()
zlog.Info(ctx).
Str("remote_addr", r.RemoteAddr).
Str("method", r.Method).
Str("request_uri", r.RequestURI).
Int("status", http.StatusTooManyRequests).
Msg("rate limited HTTP request")

apiError(w, http.StatusTooManyRequests, "server handling too many requests")
apiError(ctx, w, http.StatusTooManyRequests, "server handling too many requests")
return
}
defer sem.Release(1)
Expand Down
7 changes: 4 additions & 3 deletions httptransport/discoveryhandler.go
Expand Up @@ -21,17 +21,18 @@ var (
func DiscoveryHandler() http.Handler {
allow := []string{`application/json`, `application/vnd.oai.openapi+json`}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if r.Method != http.MethodGet {
apiError(w, http.StatusMethodNotAllowed, "endpoint only allows GET")
apiError(ctx, w, http.StatusMethodNotAllowed, "endpoint only allows GET")
return
}
switch err := pickContentType(w, r, allow); {
case errors.Is(err, nil):
case errors.Is(err, ErrMediaType):
apiError(w, http.StatusUnsupportedMediaType, "unable to negotiate common media type for %v", allow)
apiError(ctx, w, http.StatusUnsupportedMediaType, "unable to negotiate common media type for %v", allow)
return
default:
apiError(w, http.StatusInternalServerError, "unexpected error: %v", err)
apiError(ctx, w, http.StatusInternalServerError, "unexpected error: %v", err)
return
}
w.Header().Set("etag", openapiJSONEtag)
Expand Down
8 changes: 6 additions & 2 deletions httptransport/discoveryhandler_test.go
Expand Up @@ -2,6 +2,7 @@ package httptransport

import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"net/http"
Expand All @@ -14,13 +15,15 @@ import (

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/quay/zlog"
)

func TestDiscoveryEndpoint(t *testing.T) {
ctx := zlog.Test(context.Background(), t)
h := DiscoveryHandler()

r := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/openapi/v1", nil)
req := httptest.NewRequest("GET", "/openapi/v1", nil).WithContext(ctx)
req.Header.Set("Accept", "application/yaml, application/json; q=0.4, application/vnd.oai.openapi+json; q=1.0")
h.ServeHTTP(r, req)

Expand Down Expand Up @@ -50,10 +53,11 @@ func TestDiscoveryEndpoint(t *testing.T) {
}

func TestDiscoveryFailure(t *testing.T) {
ctx := zlog.Test(context.Background(), t)
h := DiscoveryHandler()

r := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/openapi/v1", nil)
req := httptest.NewRequest("GET", "/openapi/v1", nil).WithContext(ctx)
req.Header.Set("Accept", "application/yaml")
h.ServeHTTP(r, req)

Expand Down
11 changes: 10 additions & 1 deletion httptransport/error.go
Expand Up @@ -2,21 +2,30 @@ package httptransport

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/quay/zlog"
)

// ApiError writes an untyped (that is, "application/json") error with the
// provided HTTP status code and message.
func apiError(w http.ResponseWriter, code int, f string, v ...interface{}) {
func apiError(ctx context.Context, w http.ResponseWriter, code int, f string, v ...interface{}) {
const errheader = `Clair-Error`
h := w.Header()
h.Del("link")
h.Set("content-type", "application/json")
h.Set("x-content-type-options", "nosniff")
h.Set("trailer", errheader)
w.WriteHeader(code)
if ev := zlog.Debug(ctx); ev.Enabled() {
ev.
Int("code", code).
Str("error", fmt.Sprintf(f, v...)).
Msg("http error response")
}

var buf bytes.Buffer
buf.WriteString(`{"code":"`)
Expand Down
58 changes: 29 additions & 29 deletions httptransport/indexer_v1.go
Expand Up @@ -73,31 +73,31 @@ func (h *IndexerV1) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

func (h *IndexerV1) indexReport(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
switch r.Method {
case http.MethodPost:
case http.MethodDelete:
default:
apiError(w, http.StatusMethodNotAllowed, "method disallowed: %s", r.Method)
apiError(ctx, w, http.StatusMethodNotAllowed, "method disallowed: %s", r.Method)
return
}
ctx := r.Context()
defer r.Body.Close()
dec := codec.GetDecoder(r.Body)
defer codec.PutDecoder(dec)
switch r.Method {
case http.MethodPost:
state, err := h.srv.State(ctx)
if err != nil {
apiError(w, http.StatusInternalServerError, "could not retrieve indexer state: %v", err)
apiError(ctx, w, http.StatusInternalServerError, "could not retrieve indexer state: %v", err)
return
}
var m claircore.Manifest
if err := dec.Decode(&m); err != nil {
apiError(w, http.StatusBadRequest, "failed to deserialize manifest: %v", err)
apiError(ctx, w, http.StatusBadRequest, "failed to deserialize manifest: %v", err)
return
}
if m.Hash.String() == "" || len(m.Layers) == 0 {
apiError(w, http.StatusBadRequest, "bogus manifest")
apiError(ctx, w, http.StatusBadRequest, "bogus manifest")
return
}
next := path.Join(r.URL.Path, m.Hash.String())
Expand All @@ -116,10 +116,10 @@ func (h *IndexerV1) indexReport(w http.ResponseWriter, r *http.Request) {
switch {
case errors.Is(err, nil):
case errors.Is(err, tarfs.ErrFormat):
apiError(w, http.StatusBadRequest, "failed to start scan: %v", err)
apiError(ctx, w, http.StatusBadRequest, "failed to start scan: %v", err)
return
default:
apiError(w, http.StatusInternalServerError, "failed to start scan: %v", err)
apiError(ctx, w, http.StatusInternalServerError, "failed to start scan: %v", err)
return
}

Expand All @@ -133,12 +133,12 @@ func (h *IndexerV1) indexReport(w http.ResponseWriter, r *http.Request) {
case http.MethodDelete:
var ds []claircore.Digest
if err := dec.Decode(&ds); err != nil {
apiError(w, http.StatusBadRequest, "failed to deserialize bulk delete: %v", err)
apiError(ctx, w, http.StatusBadRequest, "failed to deserialize bulk delete: %v", err)
return
}
ds, err := h.srv.DeleteManifests(ctx, ds...)
if err != nil {
apiError(w, http.StatusInternalServerError, "could not delete manifests: %v", err)
apiError(ctx, w, http.StatusInternalServerError, "could not delete manifests: %v", err)
return
}
zlog.Debug(ctx).
Expand All @@ -158,35 +158,35 @@ const (
)

func (h *IndexerV1) indexReportOne(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
switch r.Method {
case http.MethodGet:
case http.MethodDelete:
default:
apiError(w, http.StatusMethodNotAllowed, "method disallowed: %s", r.Method)
apiError(ctx, w, http.StatusMethodNotAllowed, "method disallowed: %s", r.Method)
return
}
d, err := getDigest(w, r)
if err != nil {
apiError(w, http.StatusBadRequest, "malformed path: %v", err)
apiError(ctx, w, http.StatusBadRequest, "malformed path: %v", err)
return
}
ctx := r.Context()
switch r.Method {
case http.MethodGet:
allow := []string{"application/vnd.clair.indexreport.v1+json", "application/json"}
switch err := pickContentType(w, r, allow); {
case errors.Is(err, nil): // OK
case errors.Is(err, ErrMediaType):
apiError(w, http.StatusUnsupportedMediaType, "unable to negotiate common media type for %v", allow)
apiError(ctx, w, http.StatusUnsupportedMediaType, "unable to negotiate common media type for %v", allow)
return
default:
apiError(w, http.StatusBadRequest, "malformed request: %v", err)
apiError(ctx, w, http.StatusBadRequest, "malformed request: %v", err)
return
}

state, err := h.srv.State(ctx)
if err != nil {
apiError(w, http.StatusInternalServerError, "could not retrieve indexer state: %v", err)
apiError(ctx, w, http.StatusInternalServerError, "could not retrieve indexer state: %v", err)
return
}
validator := `"` + state + `"`
Expand All @@ -197,11 +197,11 @@ func (h *IndexerV1) indexReportOne(w http.ResponseWriter, r *http.Request) {

report, ok, err := h.srv.IndexReport(ctx, d)
if !ok {
apiError(w, http.StatusNotFound, "index report not found")
apiError(ctx, w, http.StatusNotFound, "index report not found")
return
}
if err != nil {
apiError(w, http.StatusInternalServerError, "could not retrieve index report: %v", err)
apiError(ctx, w, http.StatusInternalServerError, "could not retrieve index report: %v", err)
return
}

Expand All @@ -212,32 +212,32 @@ func (h *IndexerV1) indexReportOne(w http.ResponseWriter, r *http.Request) {
err = enc.Encode(report)
case http.MethodDelete:
if _, err := h.srv.DeleteManifests(ctx, d); err != nil {
apiError(w, http.StatusInternalServerError, "unable to delete manifest: %v", err)
apiError(ctx, w, http.StatusInternalServerError, "unable to delete manifest: %v", err)
return
}
w.WriteHeader(http.StatusNoContent)
}
}

func (h *IndexerV1) indexState(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if r.Method != http.MethodGet {
apiError(w, http.StatusMethodNotAllowed, "method disallowed: %s", r.Method)
apiError(ctx, w, http.StatusMethodNotAllowed, "method disallowed: %s", r.Method)
return
}
allow := []string{"application/vnd.clair.indexstate.v1+json", "application/json"}
switch err := pickContentType(w, r, allow); {
case errors.Is(err, nil): // OK
case errors.Is(err, ErrMediaType):
apiError(w, http.StatusUnsupportedMediaType, "unable to negotiate common media type for %v", allow)
apiError(ctx, w, http.StatusUnsupportedMediaType, "unable to negotiate common media type for %v", allow)
return
default:
apiError(w, http.StatusBadRequest, "malformed request: %v", err)
apiError(ctx, w, http.StatusBadRequest, "malformed request: %v", err)
return
}
ctx := r.Context()
s, err := h.srv.State(ctx)
if err != nil {
apiError(w, http.StatusInternalServerError, "could not retrieve indexer state: %v", err)
apiError(ctx, w, http.StatusInternalServerError, "could not retrieve indexer state: %v", err)
return
}

Expand All @@ -261,35 +261,35 @@ func (h *IndexerV1) indexState(w http.ResponseWriter, r *http.Request) {
}

func (h *IndexerV1) affectedManifests(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if r.Method != http.MethodPost {
apiError(w, http.StatusMethodNotAllowed, "method disallowed: %s", r.Method)
apiError(ctx, w, http.StatusMethodNotAllowed, "method disallowed: %s", r.Method)
return
}
allow := []string{"application/vnd.clair.affectedmanifests.v1+json", "application/json"}
switch err := pickContentType(w, r, allow); {
case errors.Is(err, nil): // OK
case errors.Is(err, ErrMediaType):
apiError(w, http.StatusUnsupportedMediaType, "unable to negotiate common media type for %v", allow)
apiError(ctx, w, http.StatusUnsupportedMediaType, "unable to negotiate common media type for %v", allow)
return
default:
apiError(w, http.StatusBadRequest, "malformed request: %v", err)
apiError(ctx, w, http.StatusBadRequest, "malformed request: %v", err)
return
}
ctx := r.Context()

var vulnerabilities struct {
V []claircore.Vulnerability `json:"vulnerabilities"`
}
dec := codec.GetDecoder(r.Body)
defer codec.PutDecoder(dec)
if err := dec.Decode(&vulnerabilities); err != nil {
apiError(w, http.StatusBadRequest, "failed to deserialize vulnerabilities: %v", err)
apiError(ctx, w, http.StatusBadRequest, "failed to deserialize vulnerabilities: %v", err)
return
}

affected, err := h.srv.AffectedManifests(ctx, vulnerabilities.V)
if err != nil {
apiError(w, http.StatusInternalServerError, "could not retrieve affected manifests: %v", err)
apiError(ctx, w, http.StatusInternalServerError, "could not retrieve affected manifests: %v", err)
return
}

Expand Down

0 comments on commit 7241796

Please sign in to comment.