mirror of
https://github.com/Quiq/docker-registry-ui.git
synced 2025-09-05 23:20:04 +00:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user