mirror of
https://github.com/Luzifer/gcr-clean.git
synced 2024-12-22 19:31:19 +00:00
151 lines
3.5 KiB
Go
151 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/pkg/errors"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func deleteTags(delChan <-chan deleteRequest, wg *sync.WaitGroup) {
|
|
limiter := make(chan struct{}, cfg.Parallel)
|
|
|
|
for req := range delChan {
|
|
limiter <- struct{}{}
|
|
wg.Add(1)
|
|
|
|
go func(req deleteRequest, limiter <-chan struct{}, wg *sync.WaitGroup) {
|
|
defer func() { <-limiter }()
|
|
defer wg.Done()
|
|
|
|
logger := log.WithFields(log.Fields{
|
|
"manifest": req.Digest,
|
|
"repo": req.Repo,
|
|
})
|
|
|
|
if !cfg.NoOp {
|
|
if _, err := request(http.MethodDelete, fmt.Sprintf("%s/manifests/%s", req.Repo, req.Digest)); err != nil {
|
|
logger.WithError(err).Error("Failed to delete manifest")
|
|
return
|
|
}
|
|
}
|
|
logger.WithField("noop", cfg.NoOp).Info("Manifest deleted")
|
|
}(req, limiter, wg)
|
|
}
|
|
}
|
|
|
|
func fetchRepositories(projectIDs []string, delChan chan deleteRequest, wg *sync.WaitGroup) error {
|
|
defer wg.Done()
|
|
log.Info("Fetching repositories...")
|
|
|
|
response := struct {
|
|
Repositories []string `json:"repositories"`
|
|
}{}
|
|
|
|
body, err := request(http.MethodGet, "_catalog")
|
|
if err != nil {
|
|
return errors.Wrap(err, "Could not fetch catalog")
|
|
}
|
|
|
|
if err := json.NewDecoder(body).Decode(&response); err != nil {
|
|
return errors.Wrap(err, "Unable to unmarshal JSON response")
|
|
}
|
|
|
|
for _, repo := range response.Repositories {
|
|
process := false
|
|
for _, projectID := range projectIDs {
|
|
if strings.HasPrefix(repo, projectID) {
|
|
process = true
|
|
continue
|
|
}
|
|
}
|
|
|
|
if !process {
|
|
log.WithField("repo", repo).Debug("Not in project scope, ignoring")
|
|
continue
|
|
}
|
|
|
|
wg.Add(1)
|
|
go func(repo string) {
|
|
defer wg.Done()
|
|
if err := fetchUntaggedManifests(repo, delChan, wg); err != nil {
|
|
log.WithField("repo", repo).WithError(err).Error("Unable to fetch manifests")
|
|
}
|
|
}(repo)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func fetchUntaggedManifests(repo string, delChan chan deleteRequest, wg *sync.WaitGroup) error {
|
|
body, err := request(http.MethodGet, fmt.Sprintf("%s/tags/list", repo))
|
|
if err != nil {
|
|
return errors.Wrap(err, "Unable to list tags")
|
|
}
|
|
|
|
response := struct {
|
|
Manifests map[string]struct {
|
|
Tags []string `json:"tag"`
|
|
} `json:"manifest"`
|
|
}{}
|
|
|
|
if err := json.NewDecoder(body).Decode(&response); err != nil {
|
|
return errors.Wrap(err, "Unable to unmarshal JSON response")
|
|
}
|
|
|
|
for digest, info := range response.Manifests {
|
|
if len(info.Tags) == 0 {
|
|
delChan <- deleteRequest{repo, digest}
|
|
continue
|
|
}
|
|
|
|
log.WithFields(log.Fields{
|
|
"repo": repo,
|
|
"digest": digest,
|
|
"tags": len(info.Tags),
|
|
}).Debug("Manifest has tags, ignoring")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func request(method string, path string) (io.Reader, error) {
|
|
logger := log.WithFields(log.Fields{
|
|
"method": method,
|
|
"path": path,
|
|
})
|
|
|
|
uri := fmt.Sprintf("https://%s/v2/%s", cfg.Registry, path)
|
|
|
|
req, err := http.NewRequest(method, uri, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Unable to create HTTP request")
|
|
}
|
|
|
|
req.SetBasicAuth("_json_key", getAuth())
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
logger.WithError(err).Debug("HTTP request failed")
|
|
return nil, errors.Wrap(err, "HTTP request failed")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode > 299 {
|
|
logger.WithField("status", resp.StatusCode).Debug("Status code indicated error")
|
|
return nil, errors.Errorf("HTTP request failed with status HTTP %d", resp.StatusCode)
|
|
}
|
|
|
|
logger.Debug("Request success")
|
|
|
|
buf := new(bytes.Buffer)
|
|
_, err = io.Copy(buf, resp.Body)
|
|
|
|
return buf, errors.Wrap(err, "Unable to read response body")
|
|
}
|