Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add commit tag support #9

Merged
merged 3 commits into from
Jan 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

## General

The image cleanup client is used to clean up Docker images in a Docker Registry when they are tagged using git SHA.
The image cleanup client is used to clean up Docker images in a Docker Registry when they are tagged using git SHA. The cleaning is done either using git commit hashes or tags. Defaults to hashes otherwise ```-t``` flag should be used.

This helps to save space because obsolete images are being removed from the registry.

Expand Down
64 changes: 53 additions & 11 deletions cmd/imagestream.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ type ImageStreamCleanupOptions struct {
Keep int
ImageStream string
Namespace string
Tag bool
Sorted string
}

// NewImageStreamCleanupCommand creates a cobra command to clean up an imagestream based on commits
Expand All @@ -35,14 +37,20 @@ func NewImageStreamCleanupCommand() *cobra.Command {
cmd.Flags().StringVarP(&o.RepoPath, "git-repo-path", "p", ".", "absolute path to Git repository (for current dir use: $PWD)")
cmd.Flags().StringVarP(&o.Namespace, "namespace", "n", "", "Kubernetes namespace")
cmd.Flags().IntVarP(&o.Keep, "keep", "k", 10, "keep most current <n> images")
cmd.Flags().BoolVarP(&o.Tag, "tag", "t", false, "use tags instead of commit hashes")
cmd.Flags().StringVar(&o.Sorted, "sort", string(git.SortOptionVersion), "sort tags by criteria. Allowed values: [version, alphabetical]")
return cmd
}

func (o *ImageStreamCleanupOptions) cleanupImageStreamTags(cmd *cobra.Command, args []string) {
if len(args) > 0 {
o.ImageStream = args[0]
}


if o.Tag && !git.IsValidSortValue(o.Sorted) {
log.WithField("sort_criteria", o.Sorted).Fatal("Invalid sort criteria")
}

if len(o.Namespace) == 0 {
namespace, err := kubernetes.Namespace()
if err != nil {
Expand All @@ -55,43 +63,77 @@ func (o *ImageStreamCleanupOptions) cleanupImageStreamTags(cmd *cobra.Command, a
if len(o.ImageStream) == 0 {
imageStreams, err := openshift.GetImageStreams(o.Namespace)
if err != nil {
log.WithError(err).WithField("namespace", o.Namespace).Fatal("Could not retrieve image streams.")
log.WithError(err).
WithField("namespace", o.Namespace).
Fatal("Could not retrieve image streams.")
}

log.Printf("No image stream provided as argument. Available image streams for namespace %s: %s", o.Namespace, imageStreams)

return
}

commitHashes, err := git.GetCommitHashes(o.RepoPath, o.CommitLimit)
if err != nil {
log.WithError(err).WithField("RepoPath", o.RepoPath).WithField("CommitLimit", o.CommitLimit).Fatal("Retrieving commit hashes failed.")
var matchValues []string
if o.Tag {
var err error
matchValues, err = git.GetTags(o.RepoPath, o.CommitLimit, git.SortOption(o.Sorted))
if err != nil {
log.WithError(err).
WithFields(log.Fields{
"RepoPath": o.RepoPath,
"CommitLimit": o.CommitLimit}).
Fatal("Retrieving commit tags failed.")
}
} else {
var err error
zugao marked this conversation as resolved.
Show resolved Hide resolved
matchValues, err = git.GetCommitHashes(o.RepoPath, o.CommitLimit)
if err != nil {
log.WithError(err).
WithFields(log.Fields{
"RepoPath": o.RepoPath,
"CommitLimit": o.CommitLimit}).
Fatal("Retrieving commit hashes failed.")
}
}

imageStreamTags, err := openshift.GetImageStreamTags(o.Namespace, o.ImageStream)
if err != nil {
log.WithError(err).WithField("Namespace", o.Namespace).WithField("ImageStream", o.ImageStream).Fatal("Could not retrieve image stream.")
log.WithError(err).
WithFields(log.Fields{
"Namespace": o.Namespace,
"ImageStream": o.ImageStream}).
Fatal("Could not retrieve image stream.")
}

var matchOption cleanup.MatchOption
if o.Tag {
matchOption = cleanup.MatchOptionExact
}

matchingTags := cleanup.GetTagsMatchingPrefixes(&commitHashes, &imageStreamTags)
matchingTags := cleanup.GetMatchingTags(&matchValues, &imageStreamTags, matchOption)

activeImageStreamTags, err := openshift.GetActiveImageStreamTags(o.Namespace, o.ImageStream, imageStreamTags)
if err != nil {
log.WithError(err).WithField("Namespace", o.Namespace).WithField("ImageStream", o.ImageStream).WithField("imageStreamTags", imageStreamTags).Fatal("Could not retrieve active image stream tags.")
log.WithError(err).
WithFields(log.Fields{
"Namespace": o.Namespace,
"ImageStream": o.ImageStream,
"imageStreamTags": imageStreamTags}).
Fatal("Could not retrieve active image stream tags.")
}

inactiveTags := cleanup.GetInactiveTags(&activeImageStreamTags, &matchingTags)

inactiveTags = cleanup.LimitTags(&inactiveTags, o.Keep)

log.Printf("Tags for deletion: %s", inactiveTags)
log.WithField("inactiveTags", inactiveTags).Info("Tags for deletion")

if o.Force {
for _, inactiveTag := range inactiveTags {
openshift.DeleteImageStreamTag(o.Namespace, openshift.BuildImageStreamTagName(o.ImageStream, inactiveTag))
log.Printf("Deleted image stream tag: %s", inactiveTag)
log.WithField("inactiveTag", inactiveTag).Info("Deleted image stream tag")
}
} else {
log.Println("--force was not specified. Nothing has been deleted.")
log.Info("--force was not specified. Nothing has been deleted.")
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.13
require (
github.com/ghodss/yaml v1.0.0 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/hashicorp/go-version v1.2.0
github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5
github.com/imdario/mergo v0.3.8 // indirect
github.com/json-iterator/go v1.1.8 // indirect
Expand All @@ -13,7 +14,6 @@ require (
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect
golang.org/x/sys v0.0.0-20191018095205-727590c5006e // indirect
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 // indirect
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.0.0 h1:HzcpUG60pfl43n9d2qbdi/3l1uKpAmxlfWEPWtV/QxM=
github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
Expand All @@ -79,6 +82,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
Expand Down Expand Up @@ -193,6 +198,7 @@ github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
Expand All @@ -217,6 +223,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49N
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -258,8 +265,13 @@ golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a h1:mEQZbbaBjWyLNy0tmZmgEuQAR8XOQ3hL8GYi3J/NG64=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20200107050322-53017a39ae36 h1:+eY+U4SdIdum+uGmzG+Y7oP2YWTOsFRElVIBD6K3Wgo=
golang.org/x/tools v0.0.0-20200107050322-53017a39ae36/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
Expand Down
47 changes: 35 additions & 12 deletions pkg/cleanup/cleanup.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
package cleanup

import (
log "github.com/sirupsen/logrus"
"strings"

log "github.com/sirupsen/logrus"
)

// GetTagsMatchingPrefixes returns all tags matching one of the provided prefixes
func GetTagsMatchingPrefixes(prefixes, tags *[]string) []string {
// MatchOption type defines how the tags should be matched
type MatchOption int8

const (
MatchOptionDefault MatchOption = iota
MatchOptionExact
MatchOptionPrefix
)

// GetMatchingTags returns all tags matching one of the provided prefixes
func GetMatchingTags(values, tags *[]string, matchOption MatchOption) []string {
var matchingTags []string

log.Debugf("GetTagsMatchingPrefixes | Prefixes: %s", prefixes)
log.Debugf("GetTagsMatchingPrefixes | Tags: %s", tags)
log.WithField("values", values).Debug("values")
log.WithField("tags", tags).Debug("tags")

if len(*prefixes) > 0 && len(*tags) > 0 {
for _, prefix := range *prefixes {
if len(*values) > 0 && len(*tags) > 0 {
for _, value := range *values {
for _, tag := range *tags {
if strings.HasPrefix(tag, prefix) {
if match(tag, value, matchOption) {
matchingTags = append(matchingTags, tag)
log.Debugf("GetTagsMatchingPrefixes | Tag %s has prefix %s", tag, prefix)
log.WithFields(log.Fields{
"tag": tag,
"value": value}).
Debug("Tag matched")
}
}
}
Expand All @@ -29,15 +42,15 @@ func GetTagsMatchingPrefixes(prefixes, tags *[]string) []string {
func GetInactiveTags(activeTags, tags *[]string) []string {
var inactiveTags []string

log.Debugf("GetInactiveTags | Active tags: %s", activeTags)
log.Debugf("GetInactiveTags | Tags: %s", tags)
log.WithField("activeTags", activeTags).Debug("Active tags")
log.WithField("tags", tags).Debug("Tags")

for _, tag := range *tags {
active := false
for _, activeTag := range *activeTags {
if tag == activeTag {
active = true
log.Debugf("GetInactiveTags | Tag %s is part of the active tags", tag)
log.WithField("tag", tag).Debug("The tag is part of the active tags")
break
}
}
Expand All @@ -59,3 +72,13 @@ func LimitTags(tags *[]string, keep int) []string {

return []string{}
}

func match(tag, value string, matchOption MatchOption) bool {
switch matchOption {
case MatchOptionDefault, MatchOptionPrefix:
return strings.HasPrefix(tag, value)
case MatchOptionExact:
return tag == value
}
return false
}
37 changes: 30 additions & 7 deletions pkg/cleanup/cleanup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (
"github.com/stretchr/testify/assert"
)

type GetTagsMatchingPrefixesTestCase struct {
prefixes, tags, expected []string
type GetMatchingTagsTestCase struct {
matchValues, tags, expected []string
matchOption MatchOption
}

type GetInactiveTagsTestCase struct {
Expand All @@ -19,10 +20,10 @@ type LimitTagsTestCase struct {
limit int
}

func Test_GetTagsMatchingPrefixes(t *testing.T) {
testcases := []GetTagsMatchingPrefixesTestCase{
GetTagsMatchingPrefixesTestCase{
prefixes: []string{
func Test_GetMatchingTags(t *testing.T) {
testcases := []GetMatchingTagsTestCase{
GetMatchingTagsTestCase{
matchValues: []string{
"0b81a958f590ed7ed8",
"108f2be974f8e1e5fec8bc759ecf824e81565747",
"4cb7de27c985216b8888ff6049294dae02f3282e",
Expand All @@ -45,10 +46,32 @@ func Test_GetTagsMatchingPrefixes(t *testing.T) {
"c8a693ad89e7069674eda512c553ff56d3ca2ffd-debug",
},
},
GetMatchingTagsTestCase{
matchValues: []string{
"v1.0.2",
"2.3",
"1.0",
"v3.1.2",
"v2",
},
tags: []string{
"1.0",
"3.4",
"v1.0.2",
"0.0.1",
"0.0.2",
"v2.3.0",
},
matchOption: MatchOptionExact,
expected: []string{
"v1.0.2",
"1.0",
},
},
}

for _, testcase := range testcases {
assert.Equal(t, testcase.expected, GetTagsMatchingPrefixes(&testcase.prefixes, &testcase.tags))
assert.Equal(t, testcase.expected, GetMatchingTags(&testcase.matchValues, &testcase.tags, testcase.matchOption))
}
}

Expand Down
35 changes: 35 additions & 0 deletions pkg/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package git

import (
"io"
"strings"

"gopkg.in/src-d/go-git.v4"
)
Expand Down Expand Up @@ -34,3 +35,37 @@ func GetCommitHashes(repoPath string, commitLimit int) ([]string, error) {

return commitHashes, nil
}

// GetTags returns the commit tags of a given repository ordered alphabetically or by version. If `commitLimit` is -1 all tags will be returned.
func GetTags(repoPath string, tagLimit int, sortTagBy SortOption) ([]string, error) {
var commitTags []string

repository, err := git.PlainOpen(repoPath)
if err != nil {
return nil, err
}

tagIter, err := repository.Tags()
srueg marked this conversation as resolved.
Show resolved Hide resolved
defer tagIter.Close()
if err != nil {
return nil, err
}

for i := 0; i < tagLimit || tagLimit < 0; i++ {
tag, err := tagIter.Next()

if err != nil {
if err == io.EOF {
break
}
return nil, err
}

splittedPath := strings.Split(tag.Name().String(), "/")
tagName := splittedPath[len(splittedPath)-1]

commitTags = append(commitTags, tagName)
}

return sortTags(commitTags, sortTagBy)
}
Loading