Amend tag info page, change logging.

* Amend representation of the tag info page
* Change logging library, add "-log-level" argument and put most of the logging into DEBUG mode
This commit is contained in:
Roman Vynar
2020-02-18 23:31:56 +02:00
parent dc7b2e42fc
commit 67d82c7d59
11 changed files with 199 additions and 134 deletions

View File

@@ -10,11 +10,13 @@ import (
"sync"
"time"
"github.com/hhkbp2/go-logging"
"github.com/parnurzeal/gorequest"
"github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
)
const userAgent = "docker-registry-ui"
// Client main class.
type Client struct {
url string
@@ -22,7 +24,7 @@ type Client struct {
username string
password string
request *gorequest.SuperAgent
logger logging.Logger
logger *logrus.Entry
mux sync.Mutex
tokens map[string]string
repos map[string][]string
@@ -44,7 +46,8 @@ func NewClient(url string, verifyTLS bool, username, password string) *Client {
repos: map[string][]string{},
tagCounts: map[string]int{},
}
resp, _, errs := c.request.Get(c.url+"/v2/").Set("User-Agent", "docker-registry-ui").End()
resp, _, errs := c.request.Get(c.url+"/v2/").
Set("User-Agent", userAgent).End()
if len(errs) > 0 {
c.logger.Error(errs[0])
return nil
@@ -82,14 +85,18 @@ func NewClient(url string, verifyTLS bool, username, password string) *Client {
func (c *Client) getToken(scope string) string {
// Check if we have already a token and it's not expired.
if token, ok := c.tokens[scope]; ok {
resp, _, _ := c.request.Get(c.url+"/v2/").Set("Authorization", fmt.Sprintf("Bearer %s", token)).Set("User-Agent", "docker-registry-ui").End()
resp, _, _ := c.request.Get(c.url+"/v2/").
Set("Authorization", fmt.Sprintf("Bearer %s", token)).
Set("User-Agent", userAgent).End()
if resp != nil && resp.StatusCode == 200 {
return token
}
}
request := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: !c.verifyTLS})
resp, data, errs := request.Get(fmt.Sprintf("%s&scope=%s", c.authURL, scope)).SetBasicAuth(c.username, c.password).Set("User-Agent", "docker-registry-ui").End()
resp, data, errs := request.Get(fmt.Sprintf("%s&scope=%s", c.authURL, scope)).
SetBasicAuth(c.username, c.password).
Set("User-Agent", userAgent).End()
if len(errs) > 0 {
c.logger.Error(errs[0])
return ""
@@ -105,53 +112,38 @@ func (c *Client) getToken(scope string) string {
return c.tokens[scope]
}
// callRegistry make an HTTP request to Docker registry.
func (c *Client) callRegistry(uri, scope string, manifest uint, delete bool, list bool) (string, gorequest.Response) {
endpoint := "manifest"
if list {
endpoint = "manifest.list"
}
acceptHeader := fmt.Sprintf("application/vnd.docker.distribution.%s.v%d+json", endpoint, manifest)
// callRegistry make an HTTP request to retrieve data from Docker registry.
func (c *Client) callRegistry(uri, scope, manifestFormat string) (string, gorequest.Response) {
acceptHeader := fmt.Sprintf("application/vnd.docker.distribution.%s+json", manifestFormat)
authHeader := ""
if c.authURL != "" {
authHeader = fmt.Sprintf("Bearer %s", c.getToken(scope))
}
resp, data, errs := c.request.Get(c.url+uri).Set("Accept", acceptHeader).Set("Authorization", authHeader).Set("User-Agent", "docker-registry-ui").End()
resp, data, errs := c.request.Get(c.url+uri).
Set("Accept", acceptHeader).
Set("Authorization", authHeader).
Set("User-Agent", userAgent).End()
if len(errs) > 0 {
c.logger.Error(errs[0])
return "", resp
}
c.logger.Info("GET ", uri, " ", resp.Status)
c.logger.Debugf("GET %s %s", uri, resp.Status)
// Returns 404 when no tags in the repo.
if resp.StatusCode != 200 {
return "", resp
}
// Ensure Docker-Content-Digest header is present as we use it in various places.
// The header is probably in AWS ECR case.
digest := resp.Header.Get("Docker-Content-Digest")
if digest == "" {
// Try to get digest from body instead, should be equal to what would be presented
// in Docker-Content-Digest
// Try to get digest from body instead, should be equal to what would be presented in Docker-Content-Digest.
h := crypto.SHA256.New()
h.Write([]byte(data))
resp.Header.Set("Docker-Content-Digest", fmt.Sprintf("sha256:%x", h.Sum(nil)))
}
if delete {
// Delete by manifest digest reference.
parts := strings.Split(uri, "/manifests/")
uri = parts[0] + "/manifests/" + digest
resp, _, errs := c.request.Delete(c.url+uri).Set("Accept", acceptHeader).Set("Authorization", authHeader).Set("User-Agent", "docker-registry-ui").End()
if len(errs) > 0 {
c.logger.Error(errs[0])
} else {
// Returns 202 on success.
c.logger.Info("DELETE ", uri, " (", parts[1], ") ", resp.Status)
}
return "", resp
}
return data, resp
}
@@ -183,7 +175,7 @@ func (c *Client) Repositories(useCache bool) map[string][]string {
uri := "/v2/_catalog"
c.repos = map[string][]string{}
for {
data, resp := c.callRegistry(uri, scope, 2, false, false)
data, resp := c.callRegistry(uri, scope, "manifest.v2")
if data == "" {
return c.repos
}
@@ -216,7 +208,7 @@ func (c *Client) Repositories(useCache bool) map[string][]string {
// Tags get tags for the repo.
func (c *Client) Tags(repo string) []string {
scope := fmt.Sprintf("repository:%s:*", repo)
data, _ := c.callRegistry(fmt.Sprintf("/v2/%s/tags/list", repo), scope, 2, false, false)
data, _ := c.callRegistry(fmt.Sprintf("/v2/%s/tags/list", repo), scope, "manifest.v2")
var tags []string
for _, t := range gjson.Get(data, "tags").Array() {
tags = append(tags, t.String())
@@ -224,32 +216,45 @@ func (c *Client) Tags(repo string) []string {
return tags
}
// Manifests gets manifest list entries for a tag for the repo.
func (c *Client) Manifests(repo string, tag string) []gjson.Result {
// ManifestList gets manifest list entries for a tag for the repo.
func (c *Client) ManifestList(repo, tag string) (string, []gjson.Result) {
scope := fmt.Sprintf("repository:%s:*", repo)
data, _ := c.callRegistry(fmt.Sprintf("/v2/%s/manifests/%s", repo, tag), scope, 2, false, true)
return gjson.Get(data, "manifests").Array()
uri := fmt.Sprintf("/v2/%s/manifests/%s", repo, tag)
// If manifest.list.v2 does not exist because it's a normal image,
// the registry returns manifest.v1 or manifest.v2 if requested by sha256.
info, resp := c.callRegistry(uri, scope, "manifest.list.v2")
digest := resp.Header.Get("Docker-Content-Digest")
sha256 := ""
if digest != "" {
sha256 = digest[7:]
}
c.logger.Debugf(`Received manifest.list.v2 with sha256 "%s" from %s: %s`, sha256, uri, info)
return sha256, gjson.Get(info, "manifests").Array()
}
// TagInfo get image info for the repo tag.
func (c *Client) TagInfo(repo, tag string, v1only bool) (rsha256, rinfoV1, rinfoV2 string) {
// TagInfo get image info for the repo tag or digest sha256.
func (c *Client) TagInfo(repo, tag string, v1only bool) (string, string, string) {
scope := fmt.Sprintf("repository:%s:*", repo)
infoV1, _ := c.callRegistry(fmt.Sprintf("/v2/%s/manifests/%s", repo, tag), scope, 1, false, false)
if infoV1 == "" {
return "", "", ""
}
if v1only {
uri := fmt.Sprintf("/v2/%s/manifests/%s", repo, tag)
// Note, if manifest.v1 does not exist because the image is requested by sha256,
// the registry returns manifest.v2 instead or manifest.list.v2 if it's the manifest list!
infoV1, _ := c.callRegistry(uri, scope, "manifest.v1")
c.logger.Debugf("Received manifest.v1 from %s: %s", uri, infoV1)
if infoV1 == "" || v1only {
return "", infoV1, ""
}
infoV2, resp := c.callRegistry(fmt.Sprintf("/v2/%s/manifests/%s", repo, tag), scope, 2, false, false)
// Note, if manifest.v2 does not exist because the image is in the older format (Docker 1.9),
// the registry returns manifest.v1 instead or manifest.list.v2 if it's the manifest list requested by sha256!
infoV2, resp := c.callRegistry(uri, scope, "manifest.v2")
c.logger.Debugf("Received manifest.v2 from %s: %s", uri, infoV2)
digest := resp.Header.Get("Docker-Content-Digest")
if infoV2 == "" || digest == "" {
return "", "", ""
}
sha256 := digest[7:]
c.logger.Debugf("sha256 for %s/%s is %s", repo, tag, sha256)
return sha256, infoV1, infoV2
}
@@ -261,7 +266,8 @@ func (c *Client) TagCounts() map[string]int {
// CountTags count repository tags in background regularly.
func (c *Client) CountTags(interval uint8) {
for {
c.logger.Info("Calculating tags in background...")
start := time.Now()
c.logger.Info("[CountTags] Calculating image tags...")
catalog := c.Repositories(false)
for n, repos := range catalog {
for _, r := range repos {
@@ -272,7 +278,7 @@ func (c *Client) CountTags(interval uint8) {
c.tagCounts[fmt.Sprintf("%s/%s", n, r)] = len(c.Tags(repoPath))
}
}
c.logger.Info("Tags calculation complete.")
c.logger.Infof("[CountTags] Job complete (%v).", time.Now().Sub(start))
time.Sleep(time.Duration(interval) * time.Minute)
}
}
@@ -280,5 +286,22 @@ func (c *Client) CountTags(interval uint8) {
// DeleteTag delete image tag.
func (c *Client) DeleteTag(repo, tag string) {
scope := fmt.Sprintf("repository:%s:*", repo)
c.callRegistry(fmt.Sprintf("/v2/%s/manifests/%s", repo, tag), scope, 2, true, false)
// Get sha256 digest for tag.
_, resp := c.callRegistry(fmt.Sprintf("/v2/%s/manifests/%s", repo, tag), scope, "manifest.v2")
// Delete by manifest digest reference.
authHeader := ""
if c.authURL != "" {
authHeader = fmt.Sprintf("Bearer %s", c.getToken(scope))
}
uri := fmt.Sprintf("/v2/%s/manifests/%s", repo, resp.Header.Get("Docker-Content-Digest"))
resp, _, errs := c.request.Delete(c.url+uri).
Set("Authorization", authHeader).
Set("User-Agent", userAgent).End()
if len(errs) > 0 {
c.logger.Error(errs[0])
} else {
// Returns 202 on success.
c.logger.Infof("DELETE %s (tag:%s) %s", uri, tag, resp.Status)
}
}