Skip to content

Commit f786a9d

Browse files
committed
clairctl: Add index command and content-negotiation to client
With the server's support for spdx+json, modify clairctl's client to handle it. Also, adding a dedicated index command that returns either response (clair's index report json and spdx+json). Signed-off-by: crozzy <[email protected]>
1 parent b57e664 commit f786a9d

File tree

3 files changed

+94
-76
lines changed

3 files changed

+94
-76
lines changed

cmd/clairctl/client.go

Lines changed: 92 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
1717
"github.com/quay/claircore"
1818
"github.com/quay/zlog"
19+
spdxtools "github.com/spdx/tools-golang/spdx/v2/v2_3"
1920
"github.com/tomnomnom/linkheader"
2021

2122
"github.com/quay/clair/v4/cmd"
@@ -105,7 +106,28 @@ var (
105106
errNovelManifest = errors.New("manifest unknown to the system")
106107
)
107108

108-
func (c *Client) IndexReport(ctx context.Context, id claircore.Digest, m *claircore.Manifest) error {
109+
func (c *Client) SPDXReport(ctx context.Context, id claircore.Digest, m *claircore.Manifest) (*spdxtools.Document, error) {
110+
var report = spdxtools.Document{}
111+
err := c.GetIndexReport(ctx, id, m, &report, "application/spdx+json")
112+
if err != nil {
113+
return nil, err
114+
}
115+
return &report, nil
116+
}
117+
118+
func (c *Client) IndexReport(ctx context.Context, id claircore.Digest, m *claircore.Manifest) (*claircore.IndexReport, error) {
119+
var report = claircore.IndexReport{}
120+
err := c.GetIndexReport(ctx, id, m, &report, "application/json")
121+
if err != nil {
122+
return nil, err
123+
}
124+
if !report.Success && report.Err != "" {
125+
return nil, errors.New("indexer error: " + report.Err)
126+
}
127+
return &report, nil
128+
}
129+
130+
func (c *Client) GetIndexReport(ctx context.Context, id claircore.Digest, m *claircore.Manifest, dest interface{}, mediaType string) error {
109131
var (
110132
req *http.Request
111133
res *http.Response
@@ -121,6 +143,7 @@ func (c *Client) IndexReport(ctx context.Context, id claircore.Digest, m *clairc
121143
if err != nil {
122144
return err
123145
}
146+
req.Header.Add("Accept", mediaType)
124147
res, err = c.client.Do(req)
125148
if err != nil {
126149
zlog.Debug(ctx).
@@ -130,97 +153,90 @@ func (c *Client) IndexReport(ctx context.Context, id claircore.Digest, m *clairc
130153
return err
131154
}
132155
defer res.Body.Close()
133-
ev := zlog.Debug(ctx).
156+
zlog.Debug(ctx).
134157
Str("method", res.Request.Method).
135158
Str("path", res.Request.URL.Path).
136159
Str("status", res.Status)
137-
if ev.Enabled() && res.ContentLength > 0 && res.ContentLength <= 256 {
138-
var buf bytes.Buffer
139-
buf.ReadFrom(io.LimitReader(res.Body, 256))
140-
ev.Stringer("body", &buf)
141-
}
142-
ev.Send()
143-
switch res.StatusCode {
144-
case http.StatusNotFound, http.StatusOK:
145-
case http.StatusNotModified:
146-
return nil
147-
default:
148-
return fmt.Errorf("unexpected return status: %d", res.StatusCode)
149-
}
150160

151-
if m == nil {
152-
ev := zlog.Debug(ctx).
153-
Stringer("manifest", id)
154-
if res.StatusCode == http.StatusNotFound {
155-
ev.Msg("don't have needed manifest")
156-
return errNovelManifest
161+
var rd io.Reader
162+
switch res.StatusCode {
163+
case http.StatusOK, http.StatusNotModified:
164+
rd = res.Body
165+
case http.StatusNotFound:
166+
if m == nil {
167+
ev := zlog.Debug(ctx).
168+
Stringer("manifest", id)
169+
if res.StatusCode == http.StatusNotFound {
170+
ev.Msg("don't have needed manifest")
171+
return errNovelManifest
172+
}
173+
ev.Msg("manifest may be out-of-date")
174+
return errNeedManifest
175+
}
176+
ru, err := c.host.Parse(path.Join(c.host.RequestURI(), httptransport.IndexAPIPath))
177+
if err != nil {
178+
zlog.Debug(ctx).
179+
Err(err).
180+
Msg("unable to construct index_report url")
181+
return err
157182
}
158-
ev.Msg("manifest may be out-of-date")
159-
return errNeedManifest
160-
}
161-
ru, err := c.host.Parse(path.Join(c.host.RequestURI(), httptransport.IndexAPIPath))
162-
if err != nil {
163-
zlog.Debug(ctx).
164-
Err(err).
165-
Msg("unable to construct index_report url")
166-
return err
167-
}
168183

169-
req, err = c.request(ctx, ru, http.MethodPost)
170-
if err != nil {
171-
return err
172-
}
173-
req.Body = codec.JSONReader(m)
174-
res, err = c.client.Do(req)
175-
if err != nil {
184+
req, err = c.request(ctx, ru, http.MethodPost)
185+
if err != nil {
186+
return err
187+
}
188+
req.Header.Add("Accept", mediaType)
189+
req.Body = codec.JSONReader(m)
190+
res, err = c.client.Do(req)
191+
if err != nil {
192+
zlog.Debug(ctx).
193+
Err(err).
194+
Stringer("url", req.URL).
195+
Msg("request failed")
196+
return err
197+
}
198+
defer res.Body.Close()
176199
zlog.Debug(ctx).
177-
Err(err).
178-
Stringer("url", req.URL).
179-
Msg("request failed")
180-
return err
181-
}
182-
defer res.Body.Close()
183-
zlog.Debug(ctx).
184-
Str("method", res.Request.Method).
185-
Str("path", res.Request.URL.Path).
186-
Str("status", res.Status).
187-
Send()
188-
switch res.StatusCode {
189-
case http.StatusOK:
190-
case http.StatusCreated:
191-
//
200+
Str("method", res.Request.Method).
201+
Str("path", res.Request.URL.Path).
202+
Str("status", res.Status).
203+
Send()
204+
switch res.StatusCode {
205+
case http.StatusOK:
206+
case http.StatusCreated:
207+
//
208+
default:
209+
return fmt.Errorf("unexpected return status: %d", res.StatusCode)
210+
}
211+
switch {
212+
case res.ContentLength > 0 && res.ContentLength < 32+9:
213+
// Less than the size of the digest representation, something's up.
214+
var buf bytes.Buffer
215+
// Ignore error, because what would we do with it here?
216+
ct, _ := buf.ReadFrom(res.Body)
217+
zlog.Info(ctx).
218+
Int64("size", ct).
219+
Stringer("response", &buf).
220+
Msg("body seems short")
221+
return fmt.Errorf("body seems short: %d bytes", ct)
222+
case res.ContentLength < 0: // Streaming
223+
fallthrough
224+
default:
225+
rd = res.Body
226+
}
192227
default:
193228
return fmt.Errorf("unexpected return status: %d", res.StatusCode)
194229
}
195-
var rd io.Reader
196-
switch {
197-
case res.ContentLength > 0 && res.ContentLength < 32+9:
198-
// Less than the size of the digest representation, something's up.
199-
var buf bytes.Buffer
200-
// Ignore error, because what would we do with it here?
201-
ct, _ := buf.ReadFrom(res.Body)
202-
zlog.Info(ctx).
203-
Int64("size", ct).
204-
Stringer("response", &buf).
205-
Msg("body seems short")
206-
rd = &buf
207-
case res.ContentLength < 0: // Streaming
208-
fallthrough
209-
default:
210-
rd = res.Body
211-
}
212-
var report claircore.IndexReport
230+
213231
dec := codec.GetDecoder(rd)
214232
defer codec.PutDecoder(dec)
215-
if err := dec.Decode(&report); err != nil {
233+
if err := dec.Decode(&dest); err != nil {
216234
zlog.Debug(ctx).
217235
Err(err).
218236
Msg("unable to decode json payload")
219237
return err
220238
}
221-
if !report.Success && report.Err != "" {
222-
return errors.New("indexer error: " + report.Err)
223-
}
239+
224240
if v := res.Header.Get("etag"); v != "" {
225241
ls := linkheader.ParseMultiple(res.Header[http.CanonicalHeaderKey("link")]).
226242
FilterByRel("https://projectquay.io/clair/v1/index_report")
@@ -232,6 +248,7 @@ func (c *Client) IndexReport(ctx context.Context, id claircore.Digest, m *clairc
232248
c.setValidator(ctx, u.Path, v)
233249
}
234250
}
251+
235252
return nil
236253
}
237254

cmd/clairctl/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func main() {
5858
DeleteCmd,
5959
CheckConfigCmd,
6060
AdminCmd,
61+
IndexCmd,
6162
},
6263
Flags: []cli.Flag{
6364
&cli.BoolFlag{

cmd/clairctl/report.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ func reportAction(c *cli.Context) error {
199199
zlog.Debug(ctx).
200200
Int("attempt", ct).
201201
Msg("requesting index_report")
202-
err = cc.IndexReport(ctx, d, m)
202+
_, err = cc.IndexReport(ctx, d, m)
203203
switch {
204204
case err == nil:
205205
case errors.Is(err, errNeedManifest):

0 commit comments

Comments
 (0)