-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
239 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,32 @@ | ||
# clean ur a** | ||
Docker Registry V2 image purger tool | ||
# Docker Registry V2 Helper | ||
--- | ||
This simple tool helps you to mark images filtered by glob as need to be removed in next cycle of garbage collecting | ||
|
||
To use this tool just run `docker-registry-v2-helper` with specified params: | ||
```bash | ||
root@machine:$ docker-registry-v2-helper -h | ||
|
||
Usage of docker-registry-v2-helper: | ||
-glob string | ||
Glob for image filtering by tags (If empty will be match nothing) | ||
-password string | ||
Please set Password if basic auth enabled for Docker Registry V2 | ||
-url string | ||
Docker Registry V2 address (HTTPS / HTTP) (default "https://registry.docker.io") | ||
-username string | ||
Please set Username if basic auth enabled for Docker Registry V2 | ||
``` | ||
Example params for remove all images tags that match glob `develop-*`: | ||
```bash | ||
root@machine:$ docker-registry-v2-helper -glob "develop-*" -username "user" -password "pass" -url "https://mycool.registry.com"` | ||
``` | ||
|
||
> ⚠️ After script is complete their work please notice that you neet to run garbage collection task on your registry to clear disk space: | ||
`registry garbage-collection /etc/docker/registry/config.yml` | ||
|
||
--- | ||
## TODO: | ||
- [x] Remove images tags by glob | ||
- [ ] Remove images tags between two timestamps (period) | ||
- [ ] Remove images tags by regexp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
module github.com/docker-registry-v2-cleaner | ||
|
||
go 1.17 | ||
|
||
require ( | ||
github.com/gookit/slog v0.2.1 | ||
github.com/ryanuber/go-glob v1.0.0 | ||
) | ||
|
||
require ( | ||
github.com/gookit/color v1.5.0 // indirect | ||
github.com/gookit/goutil v0.4.4 // indirect | ||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect | ||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||
github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw= | ||
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= | ||
github.com/gookit/goutil v0.4.4 h1:18xr8NKbs4LteyOHZfQ8g7JqTqZal801mAT6LERGdpE= | ||
github.com/gookit/goutil v0.4.4/go.mod h1:qlGVh0PI+WnWSjYnIocfz/7tkeogxL6+EDNP1mRe+7o= | ||
github.com/gookit/slog v0.2.1 h1:EI47PlvpoPwxwkNTYaevN6iBNjo9nBxo0aQLiq+MTdk= | ||
github.com/gookit/slog v0.2.1/go.mod h1:CsbWzAaZA2FjLuGzvfTAR/QvFisZG31deOcZ05H++NI= | ||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | ||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= | ||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= | ||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= | ||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= | ||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= | ||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= | ||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= | ||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package main | ||
|
||
import "flag" | ||
|
||
func main() { | ||
readParamsAndRun() | ||
} | ||
|
||
func readParamsAndRun() { | ||
registryURL := flag.String("url", "https://registry.docker.io", "Docker Registry V2 address (HTTPS / HTTP) ") | ||
registryUsername := flag.String("username", "", "Please set Usermame if basic auth enabled for Docker Registry V2") | ||
registryPassword := flag.String("password", "", "Please set Password if basic auth enabled for Docker Registry V2") | ||
imageTagGlob := flag.String("glob", "", "Glob for image filtering by tags (If empty will be match nothing)") | ||
flag.Parse() | ||
|
||
getAllTags(registryURL, registryUsername, registryPassword, imageTagGlob) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package main | ||
|
||
type RegistryCatalog struct { | ||
Repositories []string `json:"repositories"` | ||
} | ||
|
||
type TagsList struct { | ||
Name string `json:"name"` | ||
Tags []string `json:"tags"` | ||
} | ||
|
||
type AllTags struct { | ||
Tags []TagsList | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"strings" | ||
|
||
log "github.com/gookit/slog" | ||
"github.com/ryanuber/go-glob" | ||
) | ||
|
||
func httpDeleteBySHA(url, username, password, name, sha string) int { | ||
client := &http.Client{} | ||
|
||
req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/v2/%s/manifests/%v", url, name, strings.TrimSpace(sha)), nil) | ||
if err != nil { | ||
log.Fatal(err.Error()) | ||
} | ||
req.SetBasicAuth(username, password) | ||
|
||
resp, err := client.Do(req) | ||
if err != nil { | ||
log.Fatal(err.Error()) | ||
} | ||
|
||
return resp.StatusCode | ||
} | ||
|
||
func httpGetHeader(url, username, password, tag, imageName string) string { | ||
client := &http.Client{} | ||
|
||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v2/%s/manifests/%v", url, imageName, tag), nil) | ||
if err != nil { | ||
log.Fatal(err.Error()) | ||
} | ||
|
||
req.SetBasicAuth(username, password) | ||
req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json") | ||
|
||
resp, err := client.Do(req) | ||
if err != nil { | ||
log.Fatal(err.Error()) | ||
} | ||
return resp.Header.Get("Docker-Content-Digest") | ||
} | ||
|
||
func httpGetBody(url, username, password string) []byte { | ||
client := &http.Client{} | ||
|
||
req, err := http.NewRequest("GET", url, nil) | ||
if err != nil { | ||
log.Fatal(err.Error()) | ||
} | ||
|
||
req.SetBasicAuth(username, password) | ||
|
||
resp, err := client.Do(req) | ||
if err != nil { | ||
log.Fatal(err.Error()) | ||
} | ||
|
||
body, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
log.Fatal(err.Error()) | ||
} | ||
|
||
return body | ||
} | ||
|
||
func getImagesCatalog(url, username, password string) *RegistryCatalog { | ||
registryCatalogBody := httpGetBody(fmt.Sprintf("%s/v2/_catalog", url), username, password) | ||
var catalog *RegistryCatalog | ||
json.Unmarshal(registryCatalogBody, &catalog) | ||
|
||
return catalog | ||
} | ||
|
||
func getAllTags(url, username, password, imageTagGlob *string) { | ||
catalog := getImagesCatalog(*url, *username, *password) | ||
alltags := AllTags{} | ||
var sortedTagsArray []string | ||
sortedTags := AllTags{} | ||
|
||
for _, v := range catalog.Repositories { | ||
tagsListBody := httpGetBody(fmt.Sprintf("%s/v2/%s/tags/list", *url, v), *username, *password) | ||
var tagslist *TagsList | ||
json.Unmarshal(tagsListBody, &tagslist) | ||
alltags.Tags = append(alltags.Tags, *tagslist) | ||
} | ||
|
||
for _, taglist := range alltags.Tags { | ||
for _, tag := range taglist.Tags { | ||
if glob.Glob(*imageTagGlob, tag) { | ||
sortedTagsArray = append(sortedTagsArray, tag) | ||
} | ||
} | ||
sortedTags.Tags = append(sortedTags.Tags, TagsList{Name: taglist.Name, Tags: sortedTagsArray}) | ||
sortedTagsArray = nil | ||
} | ||
removeImageBySHA(sortedTags, url, username, password) | ||
} | ||
|
||
func removeImageBySHA(sortedTags AllTags, url, username, password *string) { | ||
counter := 0 | ||
|
||
for _, taglist := range sortedTags.Tags { | ||
for _, tag := range taglist.Tags { | ||
sha := httpGetHeader(*url, *username, *password, tag, taglist.Name) | ||
statusCode := httpDeleteBySHA(*url, *username, *password, taglist.Name, sha) | ||
|
||
if statusCode != 202 { | ||
log.Error("Error while send delete request for SHA: %s Status code: %s", sha, statusCode) | ||
log.Warnf("Address: %s/v2/%s/manifests/%s", *url, taglist.Name, sha) | ||
} else { | ||
counter += 1 | ||
log.Infof("Removed tag with SHA: %v, for image %v. Removed images count: %v", sha, taglist.Name, counter) | ||
} | ||
} | ||
} | ||
} |