mirror of
https://github.com/Quiq/docker-registry-ui.git
synced 2025-07-16 15:25:59 +00:00
Add -purge-from-repos="repo1,repo2,..." and -disable-count-tags options
This commit is contained in:
parent
8a48bd4e8b
commit
f91c3b9aca
@ -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
12
main.go
@ -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"))
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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})
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
|
Loading…
Reference in New Issue
Block a user