4 Commits

Author SHA1 Message Date
Roman Vynar
c7c3a815fb Bump github.com/docker/docker to 26.0.2 to fix 1 Dependabot alert 2024-04-19 18:10:16 +03:00
Roman Vynar
929daf733f Release 0.10.1 2024-04-19 18:07:40 +03:00
Roman Vynar
86ee1d56bd Rename -purge-from-repos to -purge-include-repos, add -purge-exclude-repos 2024-04-19 18:07:22 +03:00
Roman Vynar
d29c24a78f Make image column clickable in Event Log 2024-04-19 18:06:28 +03:00
10 changed files with 52 additions and 36 deletions

View File

@@ -1,12 +1,18 @@
## Changelog ## Changelog
## 0.10.1 (2024-04-19)
* Rename cmd flag `-purge-from-repos` to `-purge-include-repos` to purge tags only for the specified repositories.
* Add a new cmd flag `-purge-exclude-repos` to skip the specified repositories from the tag purging.
* Make image column clickable in Event Log.
### 0.10.0 (2024-04-16) ### 0.10.0 (2024-04-16)
**JUST BREAKING CHANGES** **JUST BREAKING CHANGES**
* We have made a full rewrite. Over 6 years many things have been changed. * We have made a full rewrite. Over 6 years many things have been changed.
* Renamed github/dockerhub repo from docker-registry-ui -> registry-ui * Renamed github/dockerhub repo from docker-registry-ui -> registry-ui
* Switched from doing raw http calls to github.com/google/go-containerregistry * Switched from doing raw http calls to `github.com/google/go-containerregistry`
* URLs and links are now matching the image references, no more "library" or other weird URL parts. * URLs and links are now matching the image references, no more "library" or other weird URL parts.
* No namespace or only 2-level deep concept * No namespace or only 2-level deep concept
* An arbitrary repository levels are supported * An arbitrary repository levels are supported
@@ -18,7 +24,7 @@
* Changed format of config.yml but the same concept is preserved * Changed format of config.yml but the same concept is preserved
* Event listener path has been changed from /api/events to /event-receiver and you may need to update your registry config * Event listener path has been changed from /api/events to /event-receiver and you may need to update your registry config
* Removed built-in cron scheduler for purging tags, please use the normal cron :) * Removed built-in cron scheduler for purging tags, please use the normal cron :)
* Now you can now tune the refresh of catalog and separately refresh of tag counting, disable them etc. * Now you can tune the refresh of catalog and separately refresh of tag counting, disable them etc.
* Everything has been made better! :) * Everything has been made better! :)
### 0.9.7 (2024-02-21) ### 0.9.7 (2024-02-21)

View File

@@ -1,4 +1,4 @@
FROM golang:1.22.1-alpine3.19 as builder FROM golang:1.22.2-alpine3.19 as builder
RUN apk update && \ RUN apk update && \
apk add ca-certificates git bash gcc musl-dev apk add ca-certificates git bash gcc musl-dev

2
go.mod
View File

@@ -21,7 +21,7 @@ require (
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect
github.com/docker/cli v26.0.0+incompatible // indirect github.com/docker/cli v26.0.0+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v26.0.0+incompatible // indirect github.com/docker/docker v26.0.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.1 // indirect github.com/docker/docker-credential-helpers v0.8.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect

4
go.sum
View File

@@ -14,8 +14,8 @@ github.com/docker/cli v26.0.0+incompatible h1:90BKrx1a1HKYpSnnBFR6AgDq/FqkHxwlUy
github.com/docker/cli v26.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v26.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v26.0.0+incompatible h1:Ng2qi+gdKADUa/VM+6b6YaY2nlZhk/lVJiKR/2bMudU= github.com/docker/docker v26.0.2+incompatible h1:yGVmKUFGgcxA6PXWAokO0sQL22BrQ67cgVjko8tGdXE=
github.com/docker/docker v26.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v26.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=

11
main.go
View File

@@ -23,16 +23,17 @@ func main() {
var ( var (
a apiClient a apiClient
configFile, loggingLevel string configFile, loggingLevel string
purgeFromRepos string purgeTags, purgeDryRun bool
purgeTags, purgeDryRun bool purgeIncludeRepos, purgeExcludeRepos string
) )
flag.StringVar(&configFile, "config-file", "config.yml", "path to the config file") flag.StringVar(&configFile, "config-file", "config.yml", "path to the config file")
flag.StringVar(&loggingLevel, "log-level", "info", "logging level") flag.StringVar(&loggingLevel, "log-level", "info", "logging level")
flag.BoolVar(&purgeTags, "purge-tags", false, "purge old tags instead of running a web server") flag.BoolVar(&purgeTags, "purge-tags", false, "purge old tags instead of running a web server")
flag.BoolVar(&purgeDryRun, "dry-run", false, "dry-run for purging task, does not delete anything") flag.BoolVar(&purgeDryRun, "dry-run", false, "dry-run for purging task, does not delete anything")
flag.StringVar(&purgeFromRepos, "purge-from-repos", "", "comma-separated list of repos to purge instead of all") flag.StringVar(&purgeIncludeRepos, "purge-include-repos", "", "comma-separated list of repos to purge tags from, otherwise all")
flag.StringVar(&purgeExcludeRepos, "purge-exclude-repos", "", "comma-separated list of repos to skip from purging tags, otherwise none")
flag.Parse() flag.Parse()
// Setup logging // Setup logging
@@ -56,7 +57,7 @@ func main() {
// Execute CLI task and exit. // Execute CLI task and exit.
if purgeTags { if purgeTags {
registry.PurgeOldTags(a.client, purgeDryRun, purgeFromRepos) registry.PurgeOldTags(a.client, purgeDryRun, purgeIncludeRepos, purgeExcludeRepos)
return return
} }

View File

@@ -42,11 +42,20 @@ func (p timeSlice) Swap(i, j int) {
} }
// PurgeOldTags purge old tags. // PurgeOldTags purge old tags.
func PurgeOldTags(client *Client, purgeDryRun bool, purgeFromRepos string) { func PurgeOldTags(client *Client, purgeDryRun bool, purgeIncludeRepos, purgeExcludeRepos string) {
logger := SetupLogging("registry.tasks.PurgeOldTags") logger := SetupLogging("registry.tasks.PurgeOldTags")
keepDays := viper.GetInt("purge_tags.keep_days")
keepCount := viper.GetInt("purge_tags.keep_count")
keepRegexp := viper.GetString("purge_tags.keep_regexp")
keepFromFile := viper.GetString("purge_tags.keep_from_file")
dryRunText := ""
if purgeDryRun {
logger.Warn("Dry-run mode enabled.")
dryRunText = "skipped"
}
var dataFromFile gjson.Result var dataFromFile gjson.Result
keepFromFile := viper.GetString("purge_tags.keep_from_file")
if keepFromFile != "" { if keepFromFile != "" {
if _, err := os.Stat(keepFromFile); os.IsNotExist(err) { if _, err := os.Stat(keepFromFile); os.IsNotExist(err) {
logger.Warnf("Cannot open %s: %s", keepFromFile, err) logger.Warnf("Cannot open %s: %s", keepFromFile, err)
@@ -62,21 +71,25 @@ func PurgeOldTags(client *Client, purgeDryRun bool, purgeFromRepos string) {
dataFromFile = gjson.ParseBytes(data) dataFromFile = gjson.ParseBytes(data)
} }
dryRunText := ""
if purgeDryRun {
logger.Warn("Dry-run mode enabled.")
dryRunText = "skipped"
}
catalog := []string{} catalog := []string{}
if purgeFromRepos != "" { if purgeIncludeRepos != "" {
logger.Infof("Working on repositories [%s] to scan their tags and creation dates...", purgeFromRepos) logger.Infof("Including repositories: %s", purgeIncludeRepos)
catalog = append(catalog, strings.Split(purgeFromRepos, ",")...) catalog = append(catalog, strings.Split(purgeIncludeRepos, ",")...)
} else { } else {
logger.Info("Scanning registry for repositories, tags and their creation dates...")
client.RefreshCatalog() client.RefreshCatalog()
catalog = client.GetRepos() catalog = client.GetRepos()
} }
if purgeExcludeRepos != "" {
logger.Infof("Excluding repositories: %s", purgeExcludeRepos)
tmpCatalog := []string{}
for _, repo := range catalog {
if !ItemInSlice(repo, strings.Split(purgeExcludeRepos, ",")) {
tmpCatalog = append(tmpCatalog, repo)
}
}
catalog = tmpCatalog
}
logger.Infof("Working on repositories: %s", catalog)
now := time.Now().UTC() now := time.Now().UTC()
repos := map[string]timeSlice{} repos := map[string]timeSlice{}
@@ -91,7 +104,7 @@ func PurgeOldTags(client *Client, purgeDryRun bool, purgeFromRepos string) {
imageRef := repo + ":" + tag imageRef := repo + ":" + tag
created := client.GetImageCreated(imageRef) created := client.GetImageCreated(imageRef)
if created.IsZero() { if created.IsZero() {
// Image manifest with zero creation time, e.g. cosign one // Image manifest with zero creation time, e.g. cosign w/o --record-creation-timestamp
logger.Debugf("[%s] tag with zero creation time: %s", repo, tag) logger.Debugf("[%s] tag with zero creation time: %s", repo, tag)
continue continue
} }
@@ -100,16 +113,12 @@ func PurgeOldTags(client *Client, purgeDryRun bool, purgeFromRepos string) {
} }
logger.Infof("Scanned %d repositories.", len(catalog)) logger.Infof("Scanned %d repositories.", len(catalog))
keepDays := viper.GetInt("purge_tags.keep_days")
keepCount := viper.GetInt("purge_tags.keep_count")
logger.Infof("Filtering out tags for purging: keep %d days, keep count %d", keepDays, keepCount) logger.Infof("Filtering out tags for purging: keep %d days, keep count %d", keepDays, keepCount)
keepRegexp := viper.GetString("purge_tags.keep_regexp")
if keepRegexp != "" { if keepRegexp != "" {
logger.Infof("Keeping tags matching regexp: %s", keepRegexp) logger.Infof("Keeping tags matching regexp: %s", keepRegexp)
} }
if keepFromFile != "" { if keepFromFile != "" {
logger.Infof("Keeping tags for repos from the file: %+v", dataFromFile) logger.Infof("Keeping tags from file: %+v", dataFromFile)
} }
purgeTags := map[string][]string{} purgeTags := map[string][]string{}
keepTags := map[string][]string{} keepTags := map[string][]string{}

View File

@@ -26,7 +26,7 @@
<div style="padding: 10px 0; margin-bottom: 20px"> <div style="padding: 10px 0; margin-bottom: 20px">
<div style="text-align: center; color:darkgrey"> <div style="text-align: center; color:darkgrey">
Registry UI v{{version}} | <a href="https://quiq.com" target="_blank">Quiq Inc.</a> Registry UI v{{version}} | <a href="https://quiq.com" target="_blank">Quiq Inc.</a> | <a href="https://github.com/Quiq/registry-ui" target="_blank">Github</a>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -89,7 +89,7 @@
{{end}} {* end tags *} {{end}} {* end tags *}
{{if eventsAllowed and isset(events) }} {{if eventsAllowed and isset(events) }}
<h4>Latest activity</h4> <h4>Recent Activity</h4>
<table id="datatable_events" class="table table-striped table-bordered"> <table id="datatable_events" class="table table-striped table-bordered">
<thead bgcolor="#ddd"> <thead bgcolor="#ddd">
<tr> <tr>
@@ -104,7 +104,7 @@
{{range _, e := events}} {{range _, e := events}}
<tr> <tr>
<td>{{ e.Action }}</td> <td>{{ e.Action }}</td>
{{if hasPrefix(e.Tag,"sha256") }} {{if hasPrefix(e.Tag,"sha256:") }}
<td title="{{ e.Tag }}">{{ e.Repository }}@{{ e.Tag[:19] }}...</td> <td title="{{ e.Tag }}">{{ e.Repository }}@{{ e.Tag[:19] }}...</td>
{{else}} {{else}}
<td>{{ e.Repository }}:{{ e.Tag }}</td> <td>{{ e.Repository }}:{{ e.Tag }}</td>

View File

@@ -72,10 +72,10 @@
{{range _, e := events}} {{range _, e := events}}
<tr> <tr>
<td>{{ e.Action }}</td> <td>{{ e.Action }}</td>
{{if hasPrefix(e.Tag,"sha256") }} {{if hasPrefix(e.Tag,"sha256:") }}
<td title="{{ e.Tag }}">{{ e.Repository }}@{{ e.Tag[:19] }}...</td> <td title="{{ e.Tag }}"><a href="{{ basePath }}/{{ e.Repository }}@{{ e.Tag }}">{{ e.Repository }}@{{ e.Tag[:19] }}...</a></td>
{{else}} {{else}}
<td>{{ e.Repository }}:{{ e.Tag }}</td> <td><a href="{{ basePath }}/{{ e.Repository }}:{{ e.Tag }}">{{ e.Repository }}:{{ e.Tag }}</a></td>
{{end}} {{end}}
<td>{{ e.IP }}</td> <td>{{ e.IP }}</td>
<td>{{ e.User }}</td> <td>{{ e.User }}</td>

View File

@@ -1,3 +1,3 @@
package main package main
const version = "0.10.0" const version = "0.10.1"