From b6419fe5515c50de1065379fd9d43602df62e40f Mon Sep 17 00:00:00 2001 From: stuzer05 Date: Wed, 25 Dec 2024 21:16:15 +0200 Subject: [PATCH] first commit --- .gitignore | 1 + .idea/.gitignore | 8 ++ .idea/gitea-docker-registry-prune.iml | 9 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 ++ go.mod | 17 ++++ go.sum | 47 ++++++++++ main.go | 130 ++++++++++++++++++++++++++ 8 files changed, 226 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/gitea-docker-registry-prune.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b4c3a72 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +gitea-docker-registry-prune diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/gitea-docker-registry-prune.iml b/.idea/gitea-docker-registry-prune.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/gitea-docker-registry-prune.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b4383cc --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..45731bd --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module gitea.stuzer.link/stuzer05/gitea-docker-registry-prune + +go 1.23.4 + +require ( + code.gitea.io/sdk/gitea v0.19.0 + github.com/carlmjohnson/requests v0.24.3 +) + +require ( + github.com/davidmz/go-pageant v1.0.2 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/sys v0.28.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..77de81e --- /dev/null +++ b/go.sum @@ -0,0 +1,47 @@ +code.gitea.io/sdk/gitea v0.19.0 h1:8I6s1s4RHgzxiPHhOQdgim1RWIRcr0LVMbHBjBFXq4Y= +code.gitea.io/sdk/gitea v0.19.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI= +github.com/carlmjohnson/requests v0.24.3 h1:LYcM/jVIVPkioigMjEAnBACXl2vb42TVqiC8EYNoaXQ= +github.com/carlmjohnson/requests v0.24.3/go.mod h1:duYA/jDnyZ6f3xbcF5PpZ9N8clgopubP2nK5i6MVMhU= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= +github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..7d38373 --- /dev/null +++ b/main.go @@ -0,0 +1,130 @@ +package main + +import ( + "context" + "fmt" + "os" + "slices" + "strings" + + "code.gitea.io/sdk/gitea" + "github.com/carlmjohnson/requests" +) + +func main() { + giteaUrl := os.Getenv("GITEA_URL") + giteaToken := os.Getenv("GITEA_TOKEN") + + if err := run(giteaUrl, giteaUrl, giteaToken); err != nil { + fmt.Println(err) + os.Exit(1) + } + + fmt.Println("OK") +} + +func run(giteaUrl string, registryUrl string, giteaToken string) error { + ctx := context.TODO() + + var catalog RegistryCatalog + if err := requests.URL(registryUrl).Path("/v2/_catalog").BasicAuth("", giteaToken).ToJSON(&catalog).Fetch(ctx); err != nil { + return err + } + + var images []string + var orgs []string + for _, repo := range catalog.Repositories { + srepo := strings.Split(repo, "/") + if len(srepo) == 2 { + if !slices.Contains(orgs, srepo[0]) { + orgs = append(orgs, srepo[0]) + } + + var regRepo RegistryRepository + if err := requests.URL(registryUrl).Pathf("/v2/%s/tags/list", repo).BasicAuth("", giteaToken).ToJSON(®Repo).Fetch(ctx); err != nil { + return err + } + for _, tag := range regRepo.Tags { + var regMan RegistryRepositoryManifest + if err := requests.URL(registryUrl).Pathf("/v2/%s/manifests/%s", repo, tag).BasicAuth("", giteaToken).ToJSON(®Man).Fetch(ctx); err != nil { + return err + } + for _, man := range regMan.Manifests { + images = append(images, repo+"/"+man.Digest) + } + } + } + } + + var toPurge, toKeep []string + + client, err := gitea.NewClient(giteaUrl, gitea.SetToken(giteaToken)) + if err != nil { + return err + } + + for _, org := range orgs { + page := 1 + pageSize := -1 + + for { + list, _, err := client.ListPackages(org, gitea.ListPackagesOptions{ListOptions: gitea.ListOptions{Page: page}}) + if err != nil { + return err + } + if pageSize < 0 { + pageSize = len(list) + } + if len(list) < pageSize || len(list) == 0 { + break + } + page++ + + for _, pkg := range list { + if pkg.Type == "container" && strings.HasPrefix(pkg.Version, "sha") { + if slices.Contains(images, org+"/"+pkg.Name+"/"+pkg.Version) { + toKeep = append(toKeep, org+"/"+pkg.Name+"/"+pkg.Version) + } else { + toPurge = append(toPurge, org+"/"+pkg.Name+"/"+pkg.Version) + } + + } + } + } + } + slices.Sort(images) + slices.Sort(toKeep) + + if !slices.Equal(images, toKeep) { + return fmt.Errorf("images from registry don't match with those to keep") + } + + for _, p := range toPurge { + px := strings.Split(p, "/") + if len(px) == 3 { + if _, err := client.DeletePackage(px[0], "container", px[1], px[2]); err != nil { + return err + } + fmt.Printf("removing.. \"%s/%s\" %s\n", px[0], px[1], px[2]) + } + } + + return nil +} + +type RegistryCatalog struct { + Repositories []string `json:"repositories"` +} + +type RegistryRepository struct { + Name string `json:"name"` + Tags []string `json:"tags"` +} + +type RegistryRepositoryManifest struct { + SchemaVersion int `json:"schemaVersion"` + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int `json:"size"` + Manifests []RegistryRepositoryManifest `json:"manifests"` +}