mirror of
https://github.com/goharbor/harbor
synced 2025-04-22 03:26:04 +00:00
Merge pull request #2777 from reasonerjt/fetch-timestamp-from-clairdb
provide default timestamp for all distros in system info api
This commit is contained in:
commit
22ba87338e
@ -122,3 +122,15 @@ type ClairNamespaceTimestamp struct {
|
|||||||
Namespace string `json:"namespace"`
|
Namespace string `json:"namespace"`
|
||||||
Timestamp int64 `json:"last_update"`
|
Timestamp int64 `json:"last_update"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//ClairNamespace ...
|
||||||
|
type ClairNamespace struct {
|
||||||
|
Name string `json:"Name,omitempty"`
|
||||||
|
VersionFormat string `json:"VersionFormat,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//ClairNamespaceEnvelope ...
|
||||||
|
type ClairNamespaceEnvelope struct {
|
||||||
|
Namespaces *[]ClairNamespace `json:"Namespaces,omitempty"`
|
||||||
|
Error *ClairError `json:"Error,omitempty"`
|
||||||
|
}
|
||||||
|
@ -47,6 +47,22 @@ func NewClient(endpoint string, logger *log.Logger) *Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) send(req *http.Request, expectedStatus int) ([]byte, error) {
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != expectedStatus {
|
||||||
|
return nil, fmt.Errorf("Unexpected status code: %d, text: %s", resp.StatusCode, string(b))
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ScanLayer calls Clair's API to scan a layer.
|
// ScanLayer calls Clair's API to scan a layer.
|
||||||
func (c *Client) ScanLayer(l models.ClairLayer) error {
|
func (c *Client) ScanLayer(l models.ClairLayer) error {
|
||||||
layer := models.ClairLayerEnvelope{
|
layer := models.ClairLayerEnvelope{
|
||||||
@ -57,47 +73,28 @@ func (c *Client) ScanLayer(l models.ClairLayer) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("POST", c.endpoint+"/v1/layers", bytes.NewReader(data))
|
req, err := http.NewRequest(http.MethodPost, c.endpoint+"/v1/layers", bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set(http.CanonicalHeaderKey("Content-Type"), "application/json")
|
req.Header.Set(http.CanonicalHeaderKey("Content-Type"), "application/json")
|
||||||
resp, err := c.client.Do(req)
|
_, err = c.send(req, http.StatusCreated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.logger.Infof("response code: %d", resp.StatusCode)
|
|
||||||
if resp.StatusCode != http.StatusCreated {
|
|
||||||
c.logger.Warningf("Unexpected status code: %d", resp.StatusCode)
|
|
||||||
return fmt.Errorf("Unexpected status code: %d, text: %s", resp.StatusCode, string(b))
|
|
||||||
}
|
|
||||||
c.logger.Infof("Returning.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResult calls Clair's API to get layers with detailed vulnerability list
|
// GetResult calls Clair's API to get layers with detailed vulnerability list
|
||||||
func (c *Client) GetResult(layerName string) (*models.ClairLayerEnvelope, error) {
|
func (c *Client) GetResult(layerName string) (*models.ClairLayerEnvelope, error) {
|
||||||
req, err := http.NewRequest("GET", c.endpoint+"/v1/layers/"+layerName+"?features&vulnerabilities", nil)
|
req, err := http.NewRequest(http.MethodGet, c.endpoint+"/v1/layers/"+layerName+"?features&vulnerabilities", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resp, err := c.client.Do(req)
|
b, err := c.send(req, http.StatusOK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("Unexpected status code: %d, text: %s", resp.StatusCode, string(b))
|
|
||||||
}
|
|
||||||
var res models.ClairLayerEnvelope
|
var res models.ClairLayerEnvelope
|
||||||
err = json.Unmarshal(b, &res)
|
err = json.Unmarshal(b, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -108,22 +105,14 @@ func (c *Client) GetResult(layerName string) (*models.ClairLayerEnvelope, error)
|
|||||||
|
|
||||||
// GetNotification calls Clair's API to get details of notification
|
// GetNotification calls Clair's API to get details of notification
|
||||||
func (c *Client) GetNotification(id string) (*models.ClairNotification, error) {
|
func (c *Client) GetNotification(id string) (*models.ClairNotification, error) {
|
||||||
req, err := http.NewRequest("GET", c.endpoint+"/v1/notifications/"+id+"?limit=2", nil)
|
req, err := http.NewRequest(http.MethodGet, c.endpoint+"/v1/notifications/"+id+"?limit=2", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resp, err := c.client.Do(req)
|
b, err := c.send(req, http.StatusOK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("Unexpected status code: %d, text: %s", resp.StatusCode, string(b))
|
|
||||||
}
|
|
||||||
var ne models.ClairNotificationEnvelope
|
var ne models.ClairNotificationEnvelope
|
||||||
err = json.Unmarshal(b, &ne)
|
err = json.Unmarshal(b, &ne)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -138,22 +127,36 @@ func (c *Client) GetNotification(id string) (*models.ClairNotification, error) {
|
|||||||
|
|
||||||
// DeleteNotification deletes a notification record from Clair
|
// DeleteNotification deletes a notification record from Clair
|
||||||
func (c *Client) DeleteNotification(id string) error {
|
func (c *Client) DeleteNotification(id string) error {
|
||||||
req, err := http.NewRequest("DELETE", c.endpoint+"/v1/notifications/"+id, nil)
|
req, err := http.NewRequest(http.MethodDelete, c.endpoint+"/v1/notifications/"+id, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resp, err := c.client.Do(req)
|
_, err = c.send(req, http.StatusOK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("Unexpected status code: %d, text: %s", resp.StatusCode, string(b))
|
|
||||||
}
|
|
||||||
log.Debugf("Deleted notification %s from Clair.", id)
|
log.Debugf("Deleted notification %s from Clair.", id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListNamespaces list the namespaces in Clair
|
||||||
|
func (c *Client) ListNamespaces() ([]string, error) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, c.endpoint+"/v1/namespaces", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b, err := c.send(req, http.StatusOK)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var nse models.ClairNamespaceEnvelope
|
||||||
|
err = json.Unmarshal(b, &nse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res := []string{}
|
||||||
|
for _, ns := range *nse.Namespaces {
|
||||||
|
res = append(res, ns.Name)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
@ -19,12 +19,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"sync"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common"
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
clairdao "github.com/vmware/harbor/src/common/dao/clair"
|
clairdao "github.com/vmware/harbor/src/common/dao/clair"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/clair"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
@ -48,6 +49,40 @@ type Storage struct {
|
|||||||
Free uint64 `json:"free"`
|
Free uint64 `json:"free"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//namespaces stores all name spaces on Clair, it should be initialised only once.
|
||||||
|
type clairNamespaces struct {
|
||||||
|
*sync.RWMutex
|
||||||
|
l []string
|
||||||
|
clair *clair.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n clairNamespaces) get() ([]string, error) {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
if len(n.l) == 0 {
|
||||||
|
m := make(map[string]struct{})
|
||||||
|
if n.clair == nil {
|
||||||
|
n.clair = clair.NewClient(config.ClairEndpoint(), nil)
|
||||||
|
}
|
||||||
|
list, err := n.clair.ListNamespaces()
|
||||||
|
if err != nil {
|
||||||
|
return n.l, err
|
||||||
|
}
|
||||||
|
for _, n := range list {
|
||||||
|
ns := strings.Split(n, ":")[0]
|
||||||
|
m[ns] = struct{}{}
|
||||||
|
}
|
||||||
|
for k := range m {
|
||||||
|
n.l = append(n.l, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n.l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
namespaces clairNamespaces
|
||||||
|
)
|
||||||
|
|
||||||
//GeneralInfo wraps common systeminfo for anonymous request
|
//GeneralInfo wraps common systeminfo for anonymous request
|
||||||
type GeneralInfo struct {
|
type GeneralInfo struct {
|
||||||
WithNotary bool `json:"with_notary"`
|
WithNotary bool `json:"with_notary"`
|
||||||
@ -166,26 +201,40 @@ func getClairVulnStatus() *models.ClairVulnerabilityStatus {
|
|||||||
res.OverallUTC = last
|
res.OverallUTC = last
|
||||||
log.Debugf("Clair vuln DB last update: %d", last)
|
log.Debugf("Clair vuln DB last update: %d", last)
|
||||||
}
|
}
|
||||||
|
details := []models.ClairNamespaceTimestamp{}
|
||||||
|
if res.OverallUTC > 0 {
|
||||||
l, err := dao.ListClairVulnTimestamps()
|
l, err := dao.ListClairVulnTimestamps()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to list Clair vulnerability timestamps, error:%v", err)
|
log.Errorf("Failed to list Clair vulnerability timestamps, error:%v", err)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
m := make(map[string]time.Time)
|
m := make(map[string]int64)
|
||||||
for _, e := range l {
|
for _, e := range l {
|
||||||
ns := strings.Split(e.Namespace, ":")
|
ns := strings.Split(e.Namespace, ":")
|
||||||
if ts, ok := m[ns[0]]; !ok || ts.Before(e.LastUpdate) {
|
//only returns the latest time of one distro, i.e. unbuntu:14.04 and ubuntu:15.4 shares one timestamp
|
||||||
m[ns[0]] = e.LastUpdate
|
el := e.LastUpdate.UTC().Unix()
|
||||||
|
if ts, ok := m[ns[0]]; !ok || ts < el {
|
||||||
|
m[ns[0]] = el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list, err := namespaces.get()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get namespace list from Clair, error: %v", err)
|
||||||
|
}
|
||||||
|
//For namespaces not reported by notifier, the timestamp will be the overall db timestamp.
|
||||||
|
for _, n := range list {
|
||||||
|
if _, ok := m[n]; !ok {
|
||||||
|
m[n] = res.OverallUTC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
details := []models.ClairNamespaceTimestamp{}
|
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
e := models.ClairNamespaceTimestamp{
|
e := models.ClairNamespaceTimestamp{
|
||||||
Namespace: k,
|
Namespace: k,
|
||||||
Timestamp: v.UTC().Unix(),
|
Timestamp: v,
|
||||||
}
|
}
|
||||||
details = append(details, e)
|
details = append(details, e)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
res.Details = details
|
res.Details = details
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user