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"` }