add base clair api;

Signed-off-by: Jess Frazelle <acidburn@google.com>
This commit is contained in:
Jess Frazelle 2017-02-12 16:26:00 -08:00
parent 7d3217e552
commit f0968a951b
No known key found for this signature in database
GPG key ID: 18F3685C0022BFF3
4 changed files with 218 additions and 0 deletions

72
clair/clair.go Normal file
View file

@ -0,0 +1,72 @@
package clair
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
// Clair defines the client for retriving information from the clair API.
type Clair struct {
URL string
Client *http.Client
Logf LogfCallback
}
// LogfCallback is the callback for formatting logs.
type LogfCallback func(format string, args ...interface{})
// Quiet discards logs silently.
func Quiet(format string, args ...interface{}) {}
// Log passes log messages to the logging package.
func Log(format string, args ...interface{}) {
log.Printf(format, args...)
}
// New creates a new Clair struct with the given URL and credentials.
func New(url string, debug bool) (*Clair, error) {
transport := http.DefaultTransport
errorTransport := &ErrorTransport{
Transport: transport,
}
// set the logging
logf := Quiet
if debug {
logf = Log
}
registry := &Clair{
URL: url,
Client: &http.Client{
Transport: errorTransport,
},
Logf: logf,
}
return registry, nil
}
// url returns a clair URL with the passed arguements concatenated.
func (c *Clair) url(pathTemplate string, args ...interface{}) string {
pathSuffix := fmt.Sprintf(pathTemplate, args...)
url := fmt.Sprintf("%s%s", c.URL, pathSuffix)
return url
}
func (c *Clair) getJSON(url string, response interface{}) (http.Header, error) {
resp, err := c.Client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
return nil, err
}
return resp.Header, nil
}

46
clair/errortransport.go Normal file
View file

@ -0,0 +1,46 @@
package clair
import (
"fmt"
"io/ioutil"
"net/http"
)
type httpStatusError struct {
Response *http.Response
Body []byte // Copied from `Response.Body` to avoid problems with unclosed bodies later. Nobody calls `err.Response.Body.Close()`, ever.
}
func (err *httpStatusError) Error() string {
return fmt.Sprintf("http: non-successful response (status=%v body=%q)", err.Response.StatusCode, err.Body)
}
var _ error = &httpStatusError{}
// ErrorTransport defines the data structure for returning errors from the round tripper.
type ErrorTransport struct {
Transport http.RoundTripper
}
// RoundTrip defines the round tripper for the error transport.
func (t *ErrorTransport) RoundTrip(request *http.Request) (*http.Response, error) {
resp, err := t.Transport.RoundTrip(request)
if err != nil {
return resp, err
}
if resp.StatusCode >= 500 {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("http: failed to read response body (status=%v, err=%q)", resp.StatusCode, err)
}
return nil, &httpStatusError{
Response: resp,
Body: body,
}
}
return resp, err
}

66
clair/layer.go Normal file
View file

@ -0,0 +1,66 @@
package clair
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
// GetLayer displays a Layer and optionally all of its features and vulnerabilities.
func (c *Clair) GetLayer(name string, features, vulnerabilities bool) (layer Layer, err error) {
url := c.url("/v1/layers/%s?features=%t&vulnerabilities=%t", name, features, vulnerabilities)
c.Logf("clair.layers.get url=%s name=%s", url, name)
if _, err := c.getJSON(url, &layer); err != nil {
return layer, err
}
return layer, nil
}
// PostLayer performs the analysis of a Layer from the provided path.
func (c *Clair) PostLayer(layer Layer) (respLayer Layer, err error) {
url := c.url("/v1/layers")
c.Logf("clair.layers.post url=%s name=%s", url, layer.Name)
b, err := json.Marshal(layer)
if err != nil {
return respLayer, err
}
resp, err := c.Client.Post(url, "application/json", bytes.NewReader(b))
if err != nil {
return respLayer, err
}
defer resp.Body.Close()
if err := json.NewDecoder(resp.Body).Decode(&respLayer); err != nil {
return respLayer, err
}
return respLayer, err
}
// DeleteLayer removes a layer reference from clair.
func (c *Clair) DeleteLayer(name string) error {
url := c.url("/v1/layers/%s", name)
c.Logf("clair.layers.delete url=%s name=%s", url, name)
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return err
}
resp, err := c.Client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusNotFound {
return nil
}
return fmt.Errorf("Got status code: %d", resp.StatusCode)
}

34
clair/types.go Normal file
View file

@ -0,0 +1,34 @@
package clair
// Layer represents an image layer.
type Layer struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
Path string `json:"Path,omitempty"`
Headers map[string]string `json:"Headers,omitempty"`
ParentName string `json:"ParentName,omitempty"`
Format string `json:"Format,omitempty"`
IndexedByVersion int `json:"IndexedByVersion,omitempty"`
Features []feature `json:"Features,omitempty"`
}
// Vulnerability represents vulnerability entity returned by Clair.
type Vulnerability struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
Description string `json:"Description,omitempty"`
Link string `json:"Link,omitempty"`
Severity string `json:"Severity,omitempty"`
Metadata map[string]interface{} `json:"Metadata,omitempty"`
FixedBy string `json:"FixedBy,omitempty"`
FixedIn []feature `json:"FixedIn,omitempty"`
}
type feature struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
VersionFormat string `json:"VersionFormat,omitempty"`
Version string `json:"Version,omitempty"`
Vulnerabilities []Vulnerability `json:"Vulnerabilities,omitempty"`
AddedBy string `json:"AddedBy,omitempty"`
}