1
0
2024-12-25 21:16:15 +02:00

131 lines
3.1 KiB
Go

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("<token>", 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("<token>", giteaToken).ToJSON(&regRepo).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("<token>", giteaToken).ToJSON(&regMan).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"`
}