12 Commits

Author SHA1 Message Date
Roman Vynar
628d28398f Release 0.10.3 2024-08-15 14:58:14 +03:00
Roman Vynar
674562d8d7 Upgrade go, alpine, deps 2024-08-15 14:58:00 +03:00
Roman Vynar
920e4132f0 Store IPv6 addresses correctly 2024-08-15 14:56:51 +03:00
KanagawaNezumi
6dc1408576 Add 'insecure' option to enforce use of HTTP protocol for registry requests (#78) 2024-08-15 13:17:21 +03:00
Roman Vynar
1af4694889 Fix concurrent map iteration and map write 2024-06-11 14:23:39 +03:00
Roman Vynar
bbefd03dbd Release 0.10.2 2024-05-31 18:35:42 +03:00
Roman Vynar
f7e40bece8 Allow to override any config option via environment variables using SECTION_KEY_NAME syntax 2024-05-21 17:01:44 +03:00
Roman Vynar
b49076db7c Fix repo tag count when a repo name is a prefix for another repo name(s) 2024-05-06 16:47:37 +03:00
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
15 changed files with 166 additions and 115 deletions

View File

@@ -1,12 +1,34 @@
## Changelog ## Changelog
## 0.10.3 (2024-08-15)
* Add `registry.insecure` option to the config (alternatively REGISTRY_INSECURE env var) to support non-https registries.
Thanks to @KanagawaNezumi
* Fix concurrent map iteration and write in rare cases.
* Upgrade go version to 1.22.6 and all dependencies, alpine to 3.20.
* IPv6 addresses were not displayed correctly.
In case you need to store registry events with IPv6 addresses in MySQL, you need to run `ALTER TABLE events MODIFY column ip varchar(45) NULL`.
For sqlite, you can start a new db file or migrate events manually as it doesn't support ALTER.
## 0.10.2 (2024-05-31)
* Fix repo tag count when a repo name is a prefix for another repo name(s)
* Allow to override any config option via environment variables using SECTION_KEY_NAME syntax, e.g.
LISTEN_ADDR, PERFORMANCE_TAGS_COUNT_REFRESH_INTERVAL, REGISTRY_HOSTNAME etc.
## 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 +40,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.6-alpine3.20 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
@@ -12,7 +12,7 @@ RUN go test -v ./registry && \
go build -o /opt/registry-ui *.go go build -o /opt/registry-ui *.go
FROM alpine:3.19 FROM alpine:3.20
WORKDIR /opt WORKDIR /opt
RUN apk add --no-cache ca-certificates tzdata && \ RUN apk add --no-cache ca-certificates tzdata && \

View File

@@ -27,6 +27,9 @@ Docker images [quiq/registry-ui](https://hub.docker.com/r/quiq/registry-ui/tags/
The configuration is stored in `config.yml` and the options are self-descriptive. The configuration is stored in `config.yml` and the options are self-descriptive.
You can override any config option via environment variables using SECTION_KEY_NAME syntax,
e.g. `LISTEN_ADDR`, `PERFORMANCE_TAGS_COUNT_REFRESH_INTERVAL`, `REGISTRY_HOSTNAME` etc.
### Run UI ### Run UI
docker run -d -p 8000:8000 -v /local/config.yml:/opt/config.yml:ro quiq/registry-ui docker run -d -p 8000:8000 -v /local/config.yml:/opt/config.yml:ro quiq/registry-ui

View File

@@ -21,6 +21,8 @@ performance:
registry: registry:
# Registry hostname (without protocol but may include port). # Registry hostname (without protocol but may include port).
hostname: docker-registry.local hostname: docker-registry.local
# Allow to access non-https enabled registry.
insecure: false
# Registry credentials. # Registry credentials.
# They need to have a full access to the registry. # They need to have a full access to the registry.

View File

@@ -4,6 +4,7 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@@ -26,7 +27,7 @@ const (
action CHAR(5) NULL, action CHAR(5) NULL,
repository VARCHAR(100) NULL, repository VARCHAR(100) NULL,
tag VARCHAR(100) NULL, tag VARCHAR(100) NULL,
ip VARCHAR(15) NULL, ip VARCHAR(45) NULL,
user VARCHAR(50) NULL, user VARCHAR(50) NULL,
created DATETIME NULL created DATETIME NULL
); );
@@ -112,7 +113,10 @@ func (e *EventListener) ProcessEvents(request *http.Request) {
if tag == "" { if tag == "" {
tag = i.Get("target.digest").String() tag = i.Get("target.digest").String()
} }
ip := strings.Split(i.Get("request.addr").String(), ":")[0] ip := i.Get("request.addr").String()
if x, _, _ := net.SplitHostPort(ip); x != "" {
ip = x
}
user := i.Get("actor.name").String() user := i.Get("actor.name").String()
e.logger.Debugf("Parsed event data: %s %s:%s %s %s ", action, repository, tag, ip, user) e.logger.Debugf("Parsed event data: %s %s:%s %s %s ", action, repository, tag, ip, user)

42
go.mod
View File

@@ -4,32 +4,30 @@ go 1.22.1
require ( require (
github.com/CloudyKit/jet/v6 v6.2.0 github.com/CloudyKit/jet/v6 v6.2.0
github.com/fatih/color v1.15.0 github.com/fatih/color v1.17.0
github.com/go-sql-driver/mysql v1.8.1 github.com/go-sql-driver/mysql v1.8.1
github.com/google/go-containerregistry v0.19.1 github.com/google/go-containerregistry v0.20.2
github.com/labstack/echo/v4 v4.11.4 github.com/labstack/echo/v4 v4.12.0
github.com/mattn/go-sqlite3 v1.14.22 github.com/mattn/go-sqlite3 v1.14.22
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/smartystreets/goconvey v1.8.1 github.com/smartystreets/goconvey v1.8.1
github.com/spf13/viper v1.18.2 github.com/spf13/viper v1.19.0
github.com/tidwall/gjson v1.17.1 github.com/tidwall/gjson v1.17.3
) )
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
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 v27.1.2+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-credential-helpers v0.8.2 // 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
github.com/google/go-cmp v0.6.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/klauspost/compress v1.17.8 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/labstack/gommon v0.4.2 // indirect github.com/labstack/gommon v0.4.2 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
@@ -38,15 +36,15 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/smarty/assertions v1.15.0 // indirect github.com/smarty/assertions v1.16.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
@@ -55,13 +53,13 @@ require (
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect github.com/vbatts/tar-split v0.11.5 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.22.0 // indirect golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/net v0.24.0 // indirect golang.org/x/net v0.28.0 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.19.0 // indirect golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.6.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

82
go.sum
View File

@@ -10,16 +10,14 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/cli v26.0.0+incompatible h1:90BKrx1a1HKYpSnnBFR6AgDq/FqkHxwlUyzJVPxD30I= github.com/docker/cli v27.1.2+incompatible h1:nYviRv5Y+YAKx3dFrTvS1ErkyVVunKOhoweCTE1BsnI=
github.com/docker/cli v26.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v27.1.2+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-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker v26.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@@ -30,22 +28,22 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo=
github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
@@ -65,35 +63,35 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -106,8 +104,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
@@ -121,23 +119,23 @@ github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinC
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

13
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
@@ -50,13 +51,15 @@ func main() {
if err != nil { if err != nil {
panic(fmt.Errorf("fatal error reading config file: %w", err)) panic(fmt.Errorf("fatal error reading config file: %w", err))
} }
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
// Init registry API client. // Init registry API client.
a.client = registry.NewClient() a.client = registry.NewClient()
// 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

@@ -27,6 +27,7 @@ type Client struct {
tagCountsMux sync.Mutex tagCountsMux sync.Mutex
tagCounts map[string]int tagCounts map[string]int
isCatalogReady bool isCatalogReady bool
nameOptions []name.Option
} }
type ImageInfo struct { type ImageInfo struct {
@@ -74,12 +75,19 @@ func NewClient() *Client {
puller, _ := remote.NewPuller(authOpt, remote.WithUserAgent(userAgent), remote.WithPageSize(pageSize)) puller, _ := remote.NewPuller(authOpt, remote.WithUserAgent(userAgent), remote.WithPageSize(pageSize))
pusher, _ := remote.NewPusher(authOpt, remote.WithUserAgent(userAgent)) pusher, _ := remote.NewPusher(authOpt, remote.WithUserAgent(userAgent))
insecure := viper.GetBool("registry.insecure")
nameOptions := []name.Option{}
if insecure {
nameOptions = append(nameOptions, name.Insecure)
}
c := &Client{ c := &Client{
puller: puller, puller: puller,
pusher: pusher, pusher: pusher,
logger: SetupLogging("registry.client"), logger: SetupLogging("registry.client"),
repos: []string{}, repos: []string{},
tagCounts: map[string]int{}, tagCounts: map[string]int{},
nameOptions: nameOptions,
} }
return c return c
} }
@@ -108,7 +116,7 @@ func (c *Client) RefreshCatalog() {
ctx := context.Background() ctx := context.Background()
start := time.Now() start := time.Now()
c.logger.Info("[RefreshCatalog] Started reading catalog...") c.logger.Info("[RefreshCatalog] Started reading catalog...")
registry, _ := name.NewRegistry(viper.GetString("registry.hostname")) registry, _ := name.NewRegistry(viper.GetString("registry.hostname"), c.nameOptions...)
cat, err := c.puller.Catalogger(ctx, registry) cat, err := c.puller.Catalogger(ctx, registry)
if err != nil { if err != nil {
c.logger.Errorf("[RefreshCatalog] Error fetching catalog: %s", err) c.logger.Errorf("[RefreshCatalog] Error fetching catalog: %s", err)
@@ -156,7 +164,7 @@ func (c *Client) GetRepos() []string {
// ListTags get tags for the repo // ListTags get tags for the repo
func (c *Client) ListTags(repoName string) []string { func (c *Client) ListTags(repoName string) []string {
ctx := context.Background() ctx := context.Background()
repo, _ := name.NewRepository(viper.GetString("registry.hostname") + "/" + repoName) repo, _ := name.NewRepository(viper.GetString("registry.hostname")+"/"+repoName, c.nameOptions...)
tags, err := c.puller.List(ctx, repo) tags, err := c.puller.List(ctx, repo)
if err != nil { if err != nil {
c.logger.Errorf("Error listing tags for repo %s: %s", repoName, err) c.logger.Errorf("Error listing tags for repo %s: %s", repoName, err)
@@ -170,7 +178,7 @@ func (c *Client) ListTags(repoName string) []string {
// GetImageInfo get image info by the reference - tag name or digest sha256. // GetImageInfo get image info by the reference - tag name or digest sha256.
func (c *Client) GetImageInfo(imageRef string) (ImageInfo, error) { func (c *Client) GetImageInfo(imageRef string) (ImageInfo, error) {
ctx := context.Background() ctx := context.Background()
ref, err := name.ParseReference(viper.GetString("registry.hostname") + "/" + imageRef) ref, err := name.ParseReference(viper.GetString("registry.hostname")+"/"+imageRef, c.nameOptions...)
if err != nil { if err != nil {
c.logger.Errorf("Error parsing image reference %s: %s", imageRef, err) c.logger.Errorf("Error parsing image reference %s: %s", imageRef, err)
return ImageInfo{}, err return ImageInfo{}, err
@@ -252,7 +260,7 @@ func structToMap(obj interface{}) map[string]interface{} {
func (c *Client) GetImageCreated(imageRef string) time.Time { func (c *Client) GetImageCreated(imageRef string) time.Time {
zeroTime := new(time.Time) zeroTime := new(time.Time)
ctx := context.Background() ctx := context.Background()
ref, err := name.ParseReference(viper.GetString("registry.hostname") + "/" + imageRef) ref, err := name.ParseReference(viper.GetString("registry.hostname")+"/"+imageRef, c.nameOptions...)
if err != nil { if err != nil {
c.logger.Errorf("Error parsing image reference %s: %s", imageRef, err) c.logger.Errorf("Error parsing image reference %s: %s", imageRef, err)
return *zeroTime return *zeroTime
@@ -272,19 +280,23 @@ func (c *Client) GetImageCreated(imageRef string) time.Time {
return cfg.Created.Time return cfg.Created.Time
} }
// TagCounts return map with tag counts according to the provided list of repos/sub-repos etc. // SubRepoTagCounts return map with tag counts according to the provided list of repos/sub-repos etc.
func (c *Client) TagCounts(repoPath string, repos []string) map[string]int { func (c *Client) SubRepoTagCounts(repoPath string, repos []string) map[string]int {
counts := map[string]int{} counts := map[string]int{}
for _, r := range repos { for _, r := range repos {
subRepo := r subRepo := r
if repoPath != "" { if repoPath != "" {
subRepo = repoPath + "/" + r subRepo = repoPath + "/" + r
} }
// Acquire lock to prevent concurrent map iteration and map write.
c.tagCountsMux.Lock()
for k, v := range c.tagCounts { for k, v := range c.tagCounts {
if strings.HasPrefix(k, subRepo) { if k == subRepo || strings.HasPrefix(k, subRepo+"/") {
counts[subRepo] = counts[subRepo] + v counts[subRepo] = counts[subRepo] + v
} }
} }
c.tagCountsMux.Unlock()
} }
return counts return counts
} }
@@ -306,7 +318,7 @@ func (c *Client) CountTags(interval int) {
func (c *Client) DeleteTag(repoPath, tag string) { func (c *Client) DeleteTag(repoPath, tag string) {
ctx := context.Background() ctx := context.Background()
imageRef := repoPath + ":" + tag imageRef := repoPath + ":" + tag
ref, err := name.ParseReference(viper.GetString("registry.hostname") + "/" + imageRef) ref, err := name.ParseReference(viper.GetString("registry.hostname")+"/"+imageRef, c.nameOptions...)
if err != nil { if err != nil {
c.logger.Errorf("Error parsing image reference %s: %s", imageRef, err) c.logger.Errorf("Error parsing image reference %s: %s", imageRef, err)
return return
@@ -319,7 +331,7 @@ func (c *Client) DeleteTag(repoPath, tag string) {
} }
// Parse image reference by digest now // Parse image reference by digest now
imageRefDigest := ref.Context().RepositoryStr() + "@" + descr.Digest.String() imageRefDigest := ref.Context().RepositoryStr() + "@" + descr.Digest.String()
ref, err = name.ParseReference(viper.GetString("registry.hostname") + "/" + imageRefDigest) ref, err = name.ParseReference(viper.GetString("registry.hostname")+"/"+imageRefDigest, c.nameOptions...)
if err != nil { if err != nil {
c.logger.Errorf("Error parsing image reference %s: %s", imageRefDigest, err) c.logger.Errorf("Error parsing image reference %s: %s", imageRefDigest, err)
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.3"

2
web.go
View File

@@ -76,7 +76,7 @@ func (a *apiClient) viewCatalog(c echo.Context) error {
} }
data.Set("repos", repos) data.Set("repos", repos)
data.Set("isCatalogReady", a.client.IsCatalogReady()) data.Set("isCatalogReady", a.client.IsCatalogReady())
data.Set("tagCounts", a.client.TagCounts(repoPath, repos)) data.Set("tagCounts", a.client.SubRepoTagCounts(repoPath, repos))
data.Set("tags", tags) data.Set("tags", tags)
if repoPath != "" && (len(repos) > 0 || len(tags) > 0) { if repoPath != "" && (len(repos) > 0 || len(tags) > 0) {
// Do not show events in the root of catalog. // Do not show events in the root of catalog.