230 lines
4.5 KiB
Go
230 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
|
"github.com/aws/aws-sdk-go/service/ecr"
|
|
)
|
|
|
|
var svc *ecr.ECR
|
|
|
|
// RunFlag -
|
|
type RunFlag struct {
|
|
Repo string
|
|
KeepNum int
|
|
ExcludeTag arrayFlag
|
|
ListRepo bool
|
|
}
|
|
|
|
type ImageArr []*ecr.ImageDetail
|
|
|
|
func (s ImageArr) Len() int { return len(s) }
|
|
func (s ImageArr) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
func (s ImageArr) Less(i, j int) bool {
|
|
t1 := s[i].ImagePushedAt.Unix()
|
|
t2 := s[j].ImagePushedAt.Unix()
|
|
return t1 > t2
|
|
}
|
|
|
|
type arrayFlag []string
|
|
|
|
func (i *arrayFlag) String() string {
|
|
return fmt.Sprint(*i)
|
|
}
|
|
func (i *arrayFlag) Set(val string) error {
|
|
*i = append(*i, val)
|
|
return nil
|
|
}
|
|
|
|
var runFlag RunFlag
|
|
|
|
func init() {
|
|
runFlag = RunFlag{}
|
|
flag.StringVar(&runFlag.Repo, "repo", "", "repository name")
|
|
flag.IntVar(&runFlag.KeepNum, "keep", 5, "keep number images")
|
|
flag.Var(&runFlag.ExcludeTag, "e", "exlude delete tag")
|
|
flag.BoolVar(&runFlag.ListRepo, "list-repo", false, "list all repository")
|
|
flag.Parse()
|
|
}
|
|
|
|
func main() {
|
|
accessKey := os.Getenv("AWS_ACCESS_KEY")
|
|
secretKey := os.Getenv("AWS_SECRET_KEY")
|
|
region := os.Getenv("AWS_REGION")
|
|
if len(accessKey) == 0 || len(secretKey) == 0 {
|
|
log.Fatal("aws access key or secret key env not found")
|
|
}
|
|
if len(region) == 0 {
|
|
region = "us-east-1"
|
|
}
|
|
conf := &aws.Config{
|
|
Region: aws.String(region),
|
|
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
|
|
}
|
|
sess := session.New(conf)
|
|
svc = ecr.New(sess)
|
|
|
|
if runFlag.ListRepo == true {
|
|
repos := showRepo()
|
|
for k, v := range repos.Repositories {
|
|
fmt.Printf("%d) %s\n", k, *v.RepositoryName)
|
|
}
|
|
return
|
|
}
|
|
|
|
if len(runFlag.Repo) == 0 {
|
|
log.Fatal("repository name is empty")
|
|
}
|
|
|
|
repos := showRepo(runFlag.Repo)
|
|
if len(repos.Repositories) == 0 {
|
|
log.Fatal("repository not found")
|
|
}
|
|
|
|
imgs := showImages(runFlag.Repo)
|
|
if len(imgs.ImageDetails) == 0 {
|
|
fmt.Println("no image found")
|
|
return
|
|
}
|
|
|
|
if len(imgs.ImageDetails) <= runFlag.KeepNum {
|
|
fmt.Println("image count <= keep")
|
|
return
|
|
}
|
|
|
|
iarr := ImageArr{}
|
|
for _, v := range imgs.ImageDetails {
|
|
excludeFlag := false
|
|
if len(runFlag.ExcludeTag) > 0 {
|
|
for _, v2 := range v.ImageTags {
|
|
f := false
|
|
for _, e := range runFlag.ExcludeTag {
|
|
if *v2 == e {
|
|
f = true
|
|
excludeFlag = true
|
|
break
|
|
}
|
|
}
|
|
if f == true {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !excludeFlag {
|
|
iarr = append(iarr, v)
|
|
}
|
|
}
|
|
sort.Stable(iarr)
|
|
iarr = iarr[runFlag.KeepNum:]
|
|
if len(iarr) == 0 {
|
|
fmt.Println("no delete image")
|
|
return
|
|
}
|
|
|
|
// show delete image info
|
|
for k, v := range iarr {
|
|
tags := make([]string, len(v.ImageTags))
|
|
for idx, vv := range v.ImageTags {
|
|
tags[idx] = *vv
|
|
}
|
|
fmt.Printf("%d) %s (%s)\n", k, *v.ImageDigest, strings.Join(tags, ","))
|
|
}
|
|
txt := readInput("Delete (Y/n)")
|
|
if len(txt) == 0 || regexp.MustCompile(`(?i)^y$`).Match([]byte(txt)) {
|
|
// do delete
|
|
fmt.Println("do delete")
|
|
tmp := make([]string, len(iarr))
|
|
for k, v := range iarr {
|
|
tmp[k] = *v.ImageDigest
|
|
}
|
|
deleteImages(runFlag.Repo, tmp)
|
|
} else {
|
|
fmt.Println("cancel!")
|
|
return
|
|
}
|
|
|
|
fmt.Println("finish")
|
|
}
|
|
|
|
func readInput(tip string) string {
|
|
fmt.Printf("%s : ", tip)
|
|
reader := bufio.NewReader(os.Stdin)
|
|
txt, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// fmt.Println("input text ====> ", txt)
|
|
txt = strings.Replace(txt, "\n", "", -1)
|
|
txt = strings.Trim(txt, " ")
|
|
return txt
|
|
}
|
|
|
|
func showRepo(n ...interface{}) *ecr.DescribeRepositoriesOutput {
|
|
in := &ecr.DescribeRepositoriesInput{}
|
|
|
|
if len(n) > 0 {
|
|
name, ok := n[0].(string)
|
|
if ok {
|
|
in.RepositoryNames = []*string{aws.String(name)}
|
|
}
|
|
}
|
|
|
|
out, err := svc.DescribeRepositories(in)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func showImages(repo string) *ecr.DescribeImagesOutput {
|
|
if len(repo) == 0 {
|
|
fmt.Println("repository name is empty")
|
|
return nil
|
|
}
|
|
in := &ecr.DescribeImagesInput{
|
|
RepositoryName: aws.String(repo),
|
|
}
|
|
out, err := svc.DescribeImages(in)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func deleteImages(repo string, digest []string) {
|
|
if len(repo) == 0 {
|
|
return
|
|
}
|
|
if len(digest) == 0 {
|
|
return
|
|
}
|
|
|
|
imgs := make([]*ecr.ImageIdentifier, len(digest))
|
|
for k, v := range digest {
|
|
t := &ecr.ImageIdentifier{}
|
|
t.ImageDigest = aws.String(v)
|
|
imgs[k] = t
|
|
}
|
|
|
|
in := &ecr.BatchDeleteImageInput{}
|
|
in.SetRepositoryName(repo)
|
|
in.SetImageIds(imgs)
|
|
|
|
_, err := svc.BatchDeleteImage(in)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|