Skip to content

Commit

Permalink
Docker Registry Cleaner v2 (v0.1.0)
Browse files Browse the repository at this point in the history
  • Loading branch information
solyard committed Mar 22, 2022
1 parent 9427e00 commit e0fa9bb
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 2 deletions.
34 changes: 32 additions & 2 deletions README.md
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
15 changes: 15 additions & 0 deletions go.mod
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
)
39 changes: 39 additions & 0 deletions go.sum
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=
17 changes: 17 additions & 0 deletions main.go
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)
}
14 changes: 14 additions & 0 deletions models.go
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
}
122 changes: 122 additions & 0 deletions storage-helper.go
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)
}
}
}
}

0 comments on commit e0fa9bb

Please sign in to comment.