Skip to content
This repository has been archived by the owner on Oct 14, 2020. It is now read-only.

Commit

Permalink
feat: Implement simple client for Aqua API (#15)
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Pacak <[email protected]>
  • Loading branch information
danielpacak authored Jul 15, 2020
1 parent 3af2b02 commit 1c436a8
Show file tree
Hide file tree
Showing 2 changed files with 222 additions and 0 deletions.
175 changes: 175 additions & 0 deletions pkg/aqua/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package client

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
)

var ErrNotFound = errors.New("not found")

type client struct {
baseURL string
username string
password string
httpClient *http.Client
}

func (c *client) newGetRequest(url string) (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.SetBasicAuth(c.username, c.password)
return req, nil
}

type Clientset interface {
Registries() RegistriesInterface
Images() ImagesInterface
}

type ImagesInterface interface {
Vulnerabilities(registry, repo, tag string) (VulnerabilitiesResponse, error)
}

type RegistriesInterface interface {
List() ([]RegistryResponse, error)
}

type Client struct {
registries *Registries
images *Images
}

type Authorization struct {
Basic *UsernameAndPassword
}

type UsernameAndPassword struct {
Username string
Password string
}

func NewClient(baseURL string, authorization Authorization) *Client {
httpClient := &http.Client{
Timeout: 30 * time.Second,
}
client := &client{
baseURL: baseURL,
username: authorization.Basic.Username,
password: authorization.Basic.Password,
httpClient: httpClient,
}

return &Client{
images: &Images{
client: client,
},
registries: &Registries{
client: client,
},
}
}

func (c *Client) Images() ImagesInterface {
return c.images
}

func (c *Client) Registries() RegistriesInterface {
return c.registries
}

type Images struct {
client *client
}

func (i *Images) Vulnerabilities(registry, repo, tag string) (VulnerabilitiesResponse, error) {
url := fmt.Sprintf("%s/api/v2/images/%s/%s/%s/vulnerabilities", i.client.baseURL, registry, repo, tag)

req, err := i.client.newGetRequest(url)
if err != nil {
return VulnerabilitiesResponse{}, err
}

resp, err := i.client.httpClient.Do(req)
if err != nil {
return VulnerabilitiesResponse{}, err
}
if resp.StatusCode == http.StatusNotFound {
return VulnerabilitiesResponse{}, ErrNotFound
}
if resp.StatusCode != http.StatusOK {
return VulnerabilitiesResponse{}, fmt.Errorf("unexpected response status: %s", resp.Status)
}
var vulnerabilitiesResponse VulnerabilitiesResponse
err = json.NewDecoder(resp.Body).Decode(&vulnerabilitiesResponse)
if err != nil {
return VulnerabilitiesResponse{}, err
}

return vulnerabilitiesResponse, nil
}

type Registries struct {
client *client
}

func (r *Registries) List() ([]RegistryResponse, error) {
url := fmt.Sprintf("%s/api/v1/registries", r.client.baseURL)
req, err := r.client.newGetRequest(url)
if err != nil {
return nil, err
}

resp, err := r.client.httpClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected response status: %s", resp.Status)
}
var listRegistriesResponse []RegistryResponse
err = json.NewDecoder(resp.Body).Decode(&listRegistriesResponse)
if err != nil {
return nil, err
}

return listRegistriesResponse, nil
}

type VulnerabilitiesResponse struct {
Count int `json:"count"`
Results []VulnerabilitiesResponseResult `json:"result"`
}

type VulnerabilitiesResponseResult struct {
Registry string `json:"registry"`
ImageRepositoryName string `json:"image_repository_name"`
Resource Resource `json:"resource"`
Name string `json:"name"` // e.g. CVE-2020-3910
Description string `json:"description"`
AquaSeverity string `json:"aqua_severity"`
AquaVectors string `json:"aqua_vectors"`
AquaScoringSystem string `json:"aqua_scoring_system"`
FixVersion string `json:"fix_version"`
}

type Resource struct {
Type string `json:"type"` // e.g. package
Format string `json:"format"` // e.g. deb
Path string `json:"path"`
Name string `json:"name"` // e.g. libxml2
Version string `json:"version"` // e.g. 2.9.4+dfsg1-7+b3
}

type RegistryResponse struct {
Name string `json:"name"`
Type string `json:"type"` // e.g. HUB, API
Description string `json:"description"`
URL string `json:"url"`
Prefixes []string `json:"prefixes"`
}
47 changes: 47 additions & 0 deletions pkg/aqua/client/client_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package client

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestClient(t *testing.T) {
if testing.Short() {
t.Skip("Run this test manually")
}

c := NewClient("http://aqua.domain", Authorization{
Basic: &UsernameAndPassword{"administrator", "Password12345"}})

t.Run("Should list registries", func(t *testing.T) {
registries, err := c.Registries().List()
require.NoError(t, err)
for _, registry := range registries {
t.Logf("registry: %+v", registry)
}
})

t.Run("Should get vulnerabilities from Ad Hoc Scans registry", func(t *testing.T) {
resp, err := c.Images().Vulnerabilities("Ad Hoc Scans", "core.harbor.domain/library/nginx", "1.16")
require.NoError(t, err)
for _, vulnerability := range resp.Results {
t.Logf("vulnerability: %+v", vulnerability)
}
})

t.Run("Should return error when vulnerabilities report cannot be found", func(t *testing.T) {
_, err := c.Images().Vulnerabilities("Ad Hoc Scans", "core.harbor.domain/library/nginx", "unknown")
assert.EqualError(t, err, ErrNotFound.Error())
})

t.Run("Should get vulnerabilities from Harbor registry", func(t *testing.T) {
vr, err := c.Images().Vulnerabilities("Harbor", "library/nginx", "1.16")
require.NoError(t, err)
for _, vulnerability := range vr.Results {
t.Logf("vulnerability: %+v", vulnerability)
}
})

}

0 comments on commit 1c436a8

Please sign in to comment.