go-ecr-delete-image/main.go

244 lines
4.9 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
CertProfile string
}
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.StringVar(&runFlag.CertProfile, "profile", "", "aws config profile")
flag.Parse()
}
func main() {
var cred *credentials.Credentials
_ = cred
region := os.Getenv("AWS_REGION")
if len(region) == 0 {
region = "us-east-1"
}
if len(runFlag.CertProfile) == 0 {
accessKey := os.Getenv("AWS_ACCESS_KEY")
secretKey := os.Getenv("AWS_SECRET_KEY")
if len(accessKey) == 0 || len(secretKey) == 0 {
log.Fatal("AWS_ACCESS_KEY or AWS_SECRET_KEY env not found")
}
cred = credentials.NewStaticCredentials(accessKey, secretKey, "")
} else {
cred = credentials.NewSharedCredentials("", runFlag.CertProfile)
}
conf := &aws.Config{
Region: aws.String(region),
Credentials: cred,
}
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{}
keepNum := runFlag.KeepNum
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
keepNum--
break
}
}
if f == true {
break
}
}
}
if !excludeFlag {
iarr = append(iarr, v)
}
}
if keepNum < 0 {
keepNum = 0
}
sort.Stable(iarr)
iarr = iarr[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)
}
}