Skip to content

Commit

Permalink
indexer: add State method
Browse files Browse the repository at this point in the history
This commit also adds openapi documentation for it.
  • Loading branch information
hdonnay committed Dec 13, 2019
1 parent d5a9aa9 commit 3a9ca8e
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 107 deletions.
8 changes: 2 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@ module github.com/quay/clair/v4
go 1.13

require (
github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.12.1 // indirect
github.com/quay/clair/v3 v3.0.0-pre1 // indirect
github.com/quay/claircore v0.0.9
github.com/mattn/go-sqlite3 v1.11.0 // indirect
github.com/quay/claircore v0.0.10-0.20191211195844-8a5a18affde1
github.com/rs/zerolog v1.16.0
gocloud.dev v0.18.0
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
golang.org/x/tools v0.0.0-20191210200704-1bcf67c9cb49 // indirect
google.golang.org/grpc v1.25.1 // indirect
gopkg.in/yaml.v2 v2.2.5
)
224 changes: 153 additions & 71 deletions go.sum

Large diffs are not rendered by default.

50 changes: 36 additions & 14 deletions indexer/httpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import (
"fmt"
"net/http"
"net/url"
"path"

"github.com/quay/claircore"

clairerror "github.com/quay/clair/v4/clair-error"
"github.com/quay/clair/v4/config"
"github.com/quay/claircore"
)

var _ Service = &httpClient{}

// httpClient implents the indexer service via HTTP
// HttpClient implents the indexer service via HTTP
type httpClient struct {
addr *url.URL
c *http.Client
Expand Down Expand Up @@ -47,19 +47,19 @@ func (s *httpClient) Index(ctx context.Context, manifest *claircore.Manifest) (*
return nil, &clairerror.ErrBadManifest{err}
}

url := url.URL{
Scheme: s.addr.Scheme,
Host: s.addr.Hostname(),
Path: IndexAPIPath,
u, err := s.addr.Parse(IndexAPIPath)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", url.String(), buf)
req, err := http.NewRequestWithContext(ctx, "POST", u.String(), buf)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
resp, err := s.c.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to do request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, &clairerror.ErrRequestFail{Code: resp.StatusCode, Status: resp.Status}
}
Expand All @@ -75,19 +75,19 @@ func (s *httpClient) Index(ctx context.Context, manifest *claircore.Manifest) (*

// IndexReport retrieves a IndexReport given a manifest hash string
func (s *httpClient) IndexReport(ctx context.Context, manifestHash string) (*claircore.IndexReport, bool, error) {
url := url.URL{
Scheme: s.addr.Scheme,
Host: s.addr.Hostname(),
Path: IndexReportAPIPath + manifestHash,
u, err := s.addr.Parse(path.Join(IndexReportAPIPath, manifestHash))
if err != nil {
return nil, false, fmt.Errorf("failed to create request: %v", err)
}
req, err := http.NewRequestWithContext(ctx, "GET", url.String(), nil)
req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
if err != nil {
return nil, false, fmt.Errorf("failed to create request: %v", err)
}
resp, err := s.c.Do(req)
if err != nil {
return nil, false, fmt.Errorf("failed to do request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return nil, false, nil
}
Expand All @@ -103,3 +103,25 @@ func (s *httpClient) IndexReport(ctx context.Context, manifestHash string) (*cla

return ir, true, nil
}

func (s *httpClient) State(ctx context.Context) (string, error) {
u, err := s.addr.Parse(StateAPIPath)
if err != nil {
return "", fmt.Errorf("failed to create request: %v", err)
}
req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
if err != nil {
return "", fmt.Errorf("failed to create request: %v", err)
}
resp, err := s.c.Do(req)
if err != nil {
return "", fmt.Errorf("failed to do request: %v", err)
}
defer resp.Body.Close()

buf := &bytes.Buffer{}
if _, err := buf.ReadFrom(resp.Body); err != nil {
return "", err
}
return buf.String(), nil
}
33 changes: 29 additions & 4 deletions indexer/httptransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"sort"
"strings"

"github.com/quay/claircore"
Expand All @@ -16,6 +17,7 @@ var _ http.Handler = &HTTP{}
const (
IndexAPIPath = "/api/v1/index"
IndexReportAPIPath = "/api/v1/index_report/"
StateAPIPath = "/api/v1/state"
)

type HTTP struct {
Expand All @@ -24,12 +26,12 @@ type HTTP struct {
}

func NewHTTPTransport(service Service) (*HTTP, error) {
h := &HTTP{}
h := &HTTP{
serv: service,
}
mux := http.NewServeMux()
mux.HandleFunc(IndexAPIPath, h.IndexHandler)
mux.HandleFunc(IndexReportAPIPath, h.IndexReportHandler)
h.Register(mux)
h.ServeMux = mux
h.serv = service
return h, nil
}

Expand Down Expand Up @@ -126,8 +128,31 @@ func (h *HTTP) IndexHandler(w http.ResponseWriter, r *http.Request) {
}
}

func (h *HTTP) StateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
s := h.serv.State()
tag := `"` + s + `"`
w.Header().Add("etag", tag)

es, ok := r.Header["If-None-Match"]
if ok {
if sort.Strings(es); sort.SearchStrings(es, tag) != -1 {
w.WriteHeader(http.StatusNotModified)
return
}
}
w.Header().Set("content-type", "text/plain")
// No trailing newline, so a client can't get confused about whether it
// counts or not.
fmt.Fprint(w, s)
}

// Register will register the api on a given mux.
func (h *HTTP) Register(mux *http.ServeMux) {
mux.HandleFunc(IndexAPIPath, h.IndexHandler)
mux.HandleFunc(IndexReportAPIPath, h.IndexReportHandler)
mux.HandleFunc(StateAPIPath, h.StateHandler)
}
1 change: 1 addition & 0 deletions indexer/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ import (
type Service interface {
Index(ctx context.Context, manifest *claircore.Manifest) (*claircore.IndexReport, error)
IndexReport(ctx context.Context, manifestHash string) (*claircore.IndexReport, bool, error)
State() string
}
27 changes: 17 additions & 10 deletions matcher/httptransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"net/http"
"strings"

"github.com/quay/clair/v4/indexer"
"github.com/quay/claircore"
je "github.com/quay/claircore/pkg/jsonerr"
)

Expand All @@ -20,17 +20,22 @@ const (

type HTTP struct {
*http.ServeMux
serv Service
indexer indexer.Service
serv Service
r Reporter
}

func NewHTTPTransport(service Service, indexer indexer.Service) (*HTTP, error) {
h := &HTTP{}
type Reporter interface {
IndexReport(context.Context, string) (*claircore.IndexReport, bool, error)
}

func NewHTTPTransport(service Service, r Reporter) (*HTTP, error) {
h := &HTTP{
r: r,
serv: service,
}
mux := http.NewServeMux()
mux.HandleFunc(VulnerabilityReportAPIPath, h.VulnerabilityReportHandler)
h.Register(mux)
h.ServeMux = mux
h.serv = service
h.indexer = indexer
return h, nil
}

Expand All @@ -43,6 +48,8 @@ func (h *HTTP) VulnerabilityReportHandler(w http.ResponseWriter, r *http.Request
je.Error(w, resp, http.StatusMethodNotAllowed)
return
}
ctx, done := context.WithCancel(r.Context())
defer done()

manifestHash := strings.TrimPrefix(r.URL.Path, VulnerabilityReportAPIPath)
if manifestHash == "" {
Expand All @@ -54,7 +61,7 @@ func (h *HTTP) VulnerabilityReportHandler(w http.ResponseWriter, r *http.Request
return
}

indexReport, ok, err := h.indexer.IndexReport(context.Background(), manifestHash)
indexReport, ok, err := h.r.IndexReport(ctx, manifestHash)
if !ok {
resp := &je.Response{
Code: "not-found",
Expand All @@ -73,7 +80,7 @@ func (h *HTTP) VulnerabilityReportHandler(w http.ResponseWriter, r *http.Request
return
}

vulnReport, err := h.serv.Scan(context.Background(), indexReport)
vulnReport, err := h.serv.Scan(ctx, indexReport)
if err != nil {
resp := &je.Response{
Code: "match-error",
Expand Down
35 changes: 33 additions & 2 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,31 @@ paths:
$ref: '#/components/responses/MethodNotAllowed'
500:
$ref: '#/components/responses/InternalServerError'
'/state':
get:
tags:
- Indexer
operationId: State
summary: Report the indexer's internal configuration and state.
description: |
The state endpoint returns an opaque string indicating the indexer's
internal configuration and state.
A client may be interested in this as a signal that manifests may need
to be reindexed.
responses:
200:
description: Indexer State
headers:
Etag:
description: 'Entity Tag'
schema: {type: string}
content:
text/plain:
schema:
$ref: '#/components/schemas/State'
304:
description: Indexer State Unchanged

components:
responses:
Expand Down Expand Up @@ -148,7 +173,7 @@ components:
repository_hint:
package_db: var/lib/dpkg/status
repository_hint: f17ad9208249962b7a52349494ce75ff
introduced_in: 35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a,
introduced_in: 35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a
fixed_in_version: 5.26.2-7ubuntu0.1

Vulnerability:
Expand Down Expand Up @@ -198,7 +223,7 @@ components:

IndexedPackage:
value:
id: 10,
id: 10
name: "libapt-pkg5.0"
version: "1.6.11"
kind: "binary"
Expand Down Expand Up @@ -357,6 +382,7 @@ components:
dist:
$ref: '#/components/schemas/Distribution'
repo:
type: object
fixed_in_version:
description: "A unique ID representing this vulnerability."
type: integer
Expand Down Expand Up @@ -482,3 +508,8 @@ components:
message:
type: string
description: "a message with further detail"
State:
title: State
type: string
description: an opaque identifier
example: deadbeefdeadbeefdeadbeefdeadbeef

0 comments on commit 3a9ca8e

Please sign in to comment.