1
0
Fork 0
mirror of https://github.com/Luzifer/gcr-clean.git synced 2024-10-18 05:04:21 +00:00
gcr-clean/gcr.go

152 lines
3.5 KiB
Go
Raw Permalink Normal View History

2019-02-04 14:31:36 +00:00
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")
}