ci: prune docker tags prefixed with "master-" older than 90 days on merge (#1639)

pull/1640/head
Robert Fratto 6 years ago committed by GitHub
parent 375fc86af7
commit 1fe5dc45b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      .drone/drone.jsonnet
  2. 29
      .drone/drone.yml
  3. 197
      tools/delete_tags.go

@ -178,4 +178,24 @@ local manifest(apps) = pipeline('manifest') {
},
],
},
] + [
pipeline('prune-ci-tags') {
trigger: condition('include').tagMaster,
depends_on: ['manifest'],
steps: [
{
name: 'trigger',
image: 'grafana/loki-build-image:%s' % build_image_version,
environment: {
DOCKER_USERNAME: { from_secret: 'docker_username' },
DOCKER_PASSWORD: { from_secret: 'docker_password' },
},
commands: [
'go run ./tools/delete_tags.go -max-age=2160h -repo grafana/loki -delete',
'go run ./tools/delete_tags.go -max-age=2160h -repo grafana/promtail -delete',
'go run ./tools/delete_tags.go -max-age=2160h -repo grafana/loki-canary -delete',
],
},
],
},
]

@ -576,4 +576,33 @@ trigger:
depends_on:
- manifest
---
kind: pipeline
name: prune-ci-tags
platform:
os: linux
arch: amd64
steps:
- name: trigger
image: grafana/loki-build-image:0.9.1
commands:
- go run ./tools/delete_tags.go -max-age=2160h -repo grafana/loki -delete
- go run ./tools/delete_tags.go -max-age=2160h -repo grafana/promtail -delete
- go run ./tools/delete_tags.go -max-age=2160h -repo grafana/loki-canary -delete
environment:
DOCKER_PASSWORD:
from_secret: docker_password
DOCKER_USERNAME:
from_secret: docker_username
trigger:
ref:
- refs/heads/master
- refs/tags/v*
depends_on:
- manifest
...

@ -0,0 +1,197 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"time"
)
type auth struct {
Username string `json:"username"`
Password string `json:"password"`
}
func main() {
var (
auth auth
repo string
maxAge time.Duration
filter string
doDelete bool
)
flag.BoolVar(&doDelete, "delete", false, "turn deletions on")
flag.StringVar(&auth.Username, "username", "", "username for docker hub")
flag.StringVar(&auth.Password, "password", "", "password for docker hub")
flag.StringVar(&repo, "repo", "grafana/loki", "repo to delete tags for")
flag.StringVar(&filter, "filter", "master-", "delete tags only containing this name")
flag.DurationVar(&maxAge, "max-age", 24*time.Hour*90, "delete tags older than this age")
flag.Parse()
if username := os.Getenv("DOCKER_USERNAME"); username != "" {
auth.Username = username
}
if password := os.Getenv("DOCKER_PASSWORD"); password != "" {
auth.Password = password
}
log.Printf("Using repo %s\n", repo)
log.Printf("Using search filter %s\n", filter)
log.Printf("Using max age %v\n", maxAge)
// Get an auth token
jwt, err := getJWT(auth)
if err != nil {
log.Fatalln(err)
}
tags, err := getTags(jwt, repo)
if err != nil {
log.Fatalln(err)
}
log.Printf("Discovered %d tags pre-filtering\n", len(tags))
filtered := make([]tag, 0, len(tags))
for _, t := range tags {
if !strings.Contains(t.Name, filter) {
continue
}
age := time.Since(t.LastUpdated)
if age < maxAge {
continue
}
filtered = append(filtered, t)
}
if !doDelete {
log.Printf("Should delete %d tags\n", len(filtered))
for _, t := range filtered {
fmt.Printf("%s: last updated %s\n", t.Name, t.LastUpdated)
}
} else {
log.Printf("Deleting %d tags\n", len(filtered))
for _, t := range filtered {
log.Printf("Deleting %s (last updated %s)\n", t.Name, t.LastUpdated)
if err := deleteTag(jwt, repo, t.Name); err != nil {
log.Printf("Failed to delete %s: %v", t.Name, err)
}
}
}
}
func getJWT(a auth) (string, error) {
body, err := json.Marshal(a)
if err != nil {
return "", err
}
loginURL := "https://hub.docker.com/v2/users/login"
resp, err := http.Post(loginURL, "application/json", bytes.NewReader(body))
if err != nil {
return "", err
}
if resp.StatusCode != 200 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
resp.Body.Close()
log.Fatalf("failed to log in: %v", string(body))
}
defer resp.Body.Close()
m := map[string]interface{}{}
err = json.NewDecoder(resp.Body).Decode(&m)
if err != nil {
return "", err
}
return m["token"].(string), nil
}
type tag struct {
Name string `json:"name"`
LastUpdated time.Time `json:"last_updated"`
}
type getTagResponse struct {
NextURL *string `json:"next"`
Results []tag `json:"results"`
}
func getTags(jwt string, repo string) ([]tag, error) {
var tags []tag
tagsURL := fmt.Sprintf("https://hub.docker.com/v2/repositories/%s/tags", repo)
res, err := getTagsFromURL(jwt, tagsURL)
if err != nil {
return nil, err
}
tags = append(tags, res.Results...)
for res.NextURL != nil {
res, err = getTagsFromURL(jwt, *res.NextURL)
if err != nil {
return nil, err
}
tags = append(tags, res.Results...)
}
return tags, nil
}
func getTagsFromURL(jwt string, url string) (getTagResponse, error) {
var res getTagResponse
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return res, err
}
req.Header.Add("Authorization", "JWT "+jwt)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return res, err
}
if resp.StatusCode != 200 {
return res, errors.New("failed to get tags")
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&res)
return res, err
}
func deleteTag(jwt string, repo string, tag string) error {
tagsURL := fmt.Sprintf("https://hub.docker.com/v2/repositories/%s/tags/%s/", repo, tag)
req, err := http.NewRequest(http.MethodDelete, tagsURL, nil)
if err != nil {
return err
}
req.Header.Add("Authorization", "JWT "+jwt)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
bb, err := ioutil.ReadAll(resp.Body)
if resp.StatusCode/100 != 2 {
return fmt.Errorf("resp code %d: %s", resp.StatusCode, string(bb))
}
return err
}
Loading…
Cancel
Save