Add -purge-from-repos="repo1,repo2,..." and -disable-count-tags options

This commit is contained in:
Roman Vynar 2024-02-22 18:15:18 +02:00
parent 8a48bd4e8b
commit f91c3b9aca
6 changed files with 57 additions and 21 deletions

View File

@ -1,5 +1,10 @@
## Changelog
### UNRELEASED
* Add an option to disable counting of tags if it is very slow: `-disable-count-tags`
* Add an option to specify a comma-separated list of repos to purge: `-purge-from-repos`
### 0.9.7 (2024-02-21)
* Fix timezone support: now when running a container with `TZ` env var, e.g. "-e TZ=America/Los_Angeles", it will be reflected everywhere on UI.

12
main.go
View File

@ -23,12 +23,14 @@ func main() {
var (
a apiClient
configFile, loggingLevel string
purgeTags, purgeDryRun bool
configFile, loggingLevel, purgeFromRepos string
disableCountTags, purgeTags, purgeDryRun bool
)
flag.StringVar(&configFile, "config-file", "config.yml", "path to the config file")
flag.StringVar(&loggingLevel, "log-level", "info", "logging level")
flag.BoolVar(&disableCountTags, "disable-count-tags", false, "disable counting of tags if it is very slow")
flag.BoolVar(&purgeTags, "purge-tags", false, "purge old tags instead of running a web server")
flag.StringVar(&purgeFromRepos, "purge-from-repos", "", "comma-separated list of repos to purge instead of all")
flag.BoolVar(&purgeDryRun, "dry-run", false, "dry-run for purging task, does not delete anything")
flag.Parse()
@ -50,7 +52,7 @@ func main() {
}
purgeFunc := func() {
registry.PurgeOldTags(a.client, a.config.PurgeConfig)
registry.PurgeOldTags(a.client, a.config.PurgeConfig, purgeFromRepos)
}
// Execute CLI task and exit.
@ -69,7 +71,9 @@ func main() {
}
// Count tags in background.
go a.client.CountTags(a.config.CacheRefreshInterval)
if !disableCountTags {
go a.client.CountTags(a.config.CacheRefreshInterval)
}
if a.config.EventDatabaseDriver != "sqlite3" && a.config.EventDatabaseDriver != "mysql" {
panic(fmt.Errorf("event_database_driver should be either sqlite3 or mysql"))

View File

@ -17,6 +17,8 @@ import (
const userAgent = "docker-registry-ui"
var paginationRegex = regexp.MustCompile("^<(.*?)>;.*$")
// Client main class.
type Client struct {
url string
@ -120,6 +122,8 @@ func (c *Client) getToken(scope string) string {
// callRegistry make an HTTP request to retrieve data from Docker registry.
func (c *Client) callRegistry(uri, scope, manifestFormat string) (string, gorequest.Response) {
// TODO Support OCI manifest https://github.com/opencontainers/image-spec/blob/main/manifest.md
// acceptHeader := "application/vnd.oci.image.manifest.v1+json"
acceptHeader := fmt.Sprintf("application/vnd.docker.distribution.%s+json", manifestFormat)
authHeader := ""
if c.authURL != "" {
@ -176,31 +180,25 @@ func (c *Client) Repositories(useCache bool) map[string][]string {
c.mux.Lock()
defer c.mux.Unlock()
linkRegexp := regexp.MustCompile("^<(.*?)>;.*$")
scope := "registry:catalog:*"
uri := "/v2/_catalog"
tmp := map[string][]string{}
count := 0
for {
data, resp := c.callRegistry(uri, scope, "manifest.v2")
if data == "" {
c.repos = tmp
return c.repos
break
}
for _, r := range gjson.Get(data, "repositories").Array() {
namespace := "library"
repo := r.String()
if strings.Contains(repo, "/") {
f := strings.SplitN(repo, "/", 2)
namespace = f[0]
repo = f[1]
}
namespace, repo := SplitRepoPath(r.String())
tmp[namespace] = append(tmp[namespace], repo)
count++
}
// pagination
linkHeader := resp.Header.Get("Link")
link := linkRegexp.FindStringSubmatch(linkHeader)
link := paginationRegex.FindStringSubmatch(linkHeader)
if len(link) == 2 {
// update uri and query next page
uri = link[1]
@ -210,6 +208,7 @@ func (c *Client) Repositories(useCache bool) map[string][]string {
}
}
c.repos = tmp
c.logger.Debugf("Refreshed the catalog of %d repositories.", count)
return c.repos
}
@ -286,7 +285,7 @@ func (c *Client) CountTags(interval uint8) {
c.tagCounts[fmt.Sprintf("%s/%s", n, r)] = len(c.Tags(repoPath))
}
}
c.logger.Infof("[CountTags] Job complete (%v).", time.Now().Sub(start))
c.logger.Infof("[CountTags] Job complete (%v).", time.Since(start))
time.Sleep(time.Duration(interval) * time.Minute)
}
}

View File

@ -5,6 +5,7 @@ import (
"os"
"reflect"
"sort"
"strings"
"time"
"github.com/sirupsen/logrus"
@ -58,3 +59,15 @@ func ItemInSlice(item string, slice []string) bool {
}
return false
}
// Sprit repo path by namespace and repo name
func SplitRepoPath(repoPath string) (string, string) {
namespace := "library"
repo := repoPath
if strings.Contains(repoPath, "/") {
f := strings.SplitN(repoPath, "/", 2)
namespace = f[0]
repo = f[1]
}
return namespace, repo
}

View File

@ -6,6 +6,7 @@ import (
"os"
"regexp"
"sort"
"strings"
"time"
"github.com/tidwall/gjson"
@ -48,7 +49,7 @@ func (p timeSlice) Swap(i, j int) {
}
// PurgeOldTags purge old tags.
func PurgeOldTags(client *Client, config *PurgeTagsConfig) {
func PurgeOldTags(client *Client, config *PurgeTagsConfig, purgeFromRepos string) {
logger := SetupLogging("registry.tasks.PurgeOldTags")
var keepTagsFromFile gjson.Result
@ -72,9 +73,19 @@ func PurgeOldTags(client *Client, config *PurgeTagsConfig) {
logger.Warn("Dry-run mode enabled.")
dryRunText = "skipped"
}
logger.Info("Scanning registry for repositories, tags and their creation dates...")
catalog := client.Repositories(true)
// catalog := map[string][]string{"library": []string{""}}
catalog := map[string][]string{}
if purgeFromRepos != "" {
logger.Infof("Working on repositories [%s] to scan their tags and creation dates...", purgeFromRepos)
for _, p := range strings.Split(purgeFromRepos, ",") {
namespace, repo := SplitRepoPath(p)
catalog[namespace] = append(catalog[namespace], repo)
}
} else {
logger.Info("Scanning registry for repositories, tags and their creation dates...")
catalog = client.Repositories(true)
}
now := time.Now().UTC()
repos := map[string]timeSlice{}
count := 0
@ -97,6 +108,10 @@ func PurgeOldTags(client *Client, config *PurgeTagsConfig) {
continue
}
created := gjson.Get(gjson.Get(infoV1, "history.0.v1Compatibility").String(), "created").Time()
if created.IsZero() {
// OCI manifest w/o creation time or any other case with zero time
continue
}
repos[repo] = append(repos[repo], tagData{name: tag, created: created})
}
}

View File

@ -107,7 +107,7 @@
</table>
{{end}}
{{if not isDigest}}
{{if not isDigest && layersV1}}
<h4>Image History <!-- Manifest v2 schema 1--></h4>
{{range index, layer := layersV1}}
<table class="table table-striped table-bordered">