diff --git a/README.md b/README.md index 5c89253..dbb5af4 100644 --- a/README.md +++ b/README.md @@ -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 cleaning is done either using git commit hashes or tags. Defaults to hashes otherwise ```-t``` flag should be used. +The image cleanup client is used to clean up container images in an image registry when they are tagged using git SHA. The cleaning is done either using git commit hashes or tags. Defaults to hashes otherwise ```--tag``` flag should be used. The tool also allows to clean orphan image stream tags using ```--orphan``` flag, the orphan image stream tags are images that do not have any Git commit/tag. There are secondary flags which help to norrow the cleaning process, for more information use ```--help```. This helps to save space because obsolete images are being removed from the registry. diff --git a/cmd/imagestream.go b/cmd/imagestream.go index 11d24ad..f2e6993 100644 --- a/cmd/imagestream.go +++ b/cmd/imagestream.go @@ -2,24 +2,30 @@ package cmd import ( log "github.com/sirupsen/logrus" + "time" + "regexp" "github.com/appuio/image-cleanup/pkg/cleanup" "github.com/appuio/image-cleanup/pkg/git" "github.com/appuio/image-cleanup/pkg/kubernetes" "github.com/appuio/image-cleanup/pkg/openshift" "github.com/spf13/cobra" + "github.com/karrick/tparse" ) // ImageStreamCleanupOptions is a struct to support the cleanup command type ImageStreamCleanupOptions struct { - Force bool - CommitLimit int - RepoPath string - Keep int - ImageStream string - Namespace string - Tag bool - Sorted string + Force bool + CommitLimit int + RepoPath string + Keep int + ImageStream string + Namespace string + Tag bool + Sorted string + Orphan bool + OlderThan string + OrphanIncludeRegex string } // NewImageStreamCleanupCommand creates a cobra command to clean up an imagestream based on commits @@ -33,12 +39,15 @@ func NewImageStreamCleanupCommand() *cobra.Command { Run: o.cleanupImageStreamTags, } cmd.Flags().BoolVarP(&o.Force, "force", "f", false, "delete image stream tags") - cmd.Flags().IntVarP(&o.CommitLimit, "git-commit-limit", "l", 100, "only look at the first commits to compare with tags or use -1 for all commits") + cmd.Flags().IntVarP(&o.CommitLimit, "git-commit-limit", "l", 0, "only look at the first commits to compare with tags or use 0 for all commits") 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 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]") + cmd.Flags().BoolVarP(&o.Orphan, "orphan", "o", false, "delete images that do not match any git commit") + cmd.Flags().StringVar(&o.OlderThan, "older-than", "", "delete images that are older than the duration. Ex.: [1y2mo3w4d5h6m7s]") + cmd.Flags().StringVarP(&o.OrphanIncludeRegex, "orphan-deletion-pattern", "i", "^[a-z0-9]{40}$", "delete images that match the regex, works only with the -o flag, defaults to matching Git SHA commits") return cmd } @@ -47,9 +56,11 @@ func (o *ImageStreamCleanupOptions) cleanupImageStreamTags(cmd *cobra.Command, a o.ImageStream = args[0] } - if o.Tag && !git.IsValidSortValue(o.Sorted) { - log.WithField("sort_criteria", o.Sorted).Fatal("Invalid sort criteria") - } + validateFlagCombinationInput(o) + + orphanIncludeRegex := parseOrphanIncludeRegex(o.OrphanIncludeRegex) + + cutOffDateTime := parseCutOffDateTime(o.OlderThan) if len(o.Namespace) == 0 { namespace, err := kubernetes.Namespace() @@ -96,7 +107,7 @@ func (o *ImageStreamCleanupOptions) cleanupImageStreamTags(cmd *cobra.Command, a } } - imageStreamTags, err := openshift.GetImageStreamTags(o.Namespace, o.ImageStream) + imageStreamObjectTags, err := openshift.GetImageStreamTags(o.Namespace, o.ImageStream) if err != nil { log.WithError(err). WithFields(log.Fields{ @@ -105,12 +116,20 @@ func (o *ImageStreamCleanupOptions) cleanupImageStreamTags(cmd *cobra.Command, a Fatal("Could not retrieve image stream.") } + imageStreamTags := cleanup.FilterImageTagsByTime(&imageStreamObjectTags, cutOffDateTime) + var matchOption cleanup.MatchOption if o.Tag { matchOption = cleanup.MatchOptionExact } - matchingTags := cleanup.GetMatchingTags(&matchValues, &imageStreamTags, matchOption) + var matchingTags []string + if o.Orphan { + matchingTags = cleanup.GetOrphanImageTags(&matchValues, &imageStreamTags, matchOption) + matchingTags = cleanup.FilterByRegex(&imageStreamTags, orphanIncludeRegex) + } else { + matchingTags = cleanup.GetMatchingTags(&matchValues, &imageStreamTags, matchOption) + } activeImageStreamTags, err := openshift.GetActiveImageStreamTags(o.Namespace, o.ImageStream, imageStreamTags) if err != nil { @@ -137,3 +156,44 @@ func (o *ImageStreamCleanupOptions) cleanupImageStreamTags(cmd *cobra.Command, a log.Info("--force was not specified. Nothing has been deleted.") } } + +func validateFlagCombinationInput(o *ImageStreamCleanupOptions) { + + if o.Orphan == false && o.OrphanIncludeRegex != "^[a-z0-9]{40}$" { + log.WithFields(log.Fields{"Orphan": o.Orphan, "Regex": o.OrphanIncludeRegex}). + Fatal("Missing Orphan flag") + } + + if o.Tag && !git.IsValidSortValue(o.Sorted) { + log.WithField("sort_criteria", o.Sorted).Fatal("Invalid sort criteria.") + } + + if o.CommitLimit !=0 && o.Orphan == true { + log.WithFields(log.Fields{"CommitLimit": o.CommitLimit, "Orphan": o.Orphan}). + Fatal("Mutually exclusive flags") + } +} + +func parseOrphanIncludeRegex(orphanIncludeRegex string) *regexp.Regexp { + regexp, err := regexp.Compile(orphanIncludeRegex) + if err != nil { + log.WithField("orphanIncludeRegex", orphanIncludeRegex). + Fatal("Invalid orphan include regex.") + } + + return regexp +} + +func parseCutOffDateTime(olderThan string) time.Time { + if len(olderThan) > 0 { + cutOffDateTime, err := tparse.ParseNow(time.RFC3339, "now-" + olderThan) + if err != nil { + log.WithError(err). + WithField("--older-than", olderThan). + Fatal("Could not parse --older-than flag.") + } + return cutOffDateTime; + } + + return time.Now() +} diff --git a/go.mod b/go.mod index 2c85cda..9729c8f 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,8 @@ require ( 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 - github.com/openshift/api v3.9.1-0.20190322043348-8741ff068a47+incompatible // indirect + github.com/karrick/tparse v2.4.2+incompatible + github.com/openshift/api v3.9.1-0.20190322043348-8741ff068a47+incompatible github.com/openshift/client-go v0.0.0-20180830153425-431ec9a26e50 github.com/sirupsen/logrus v1.4.2 github.com/spf13/cobra v0.0.5 diff --git a/go.sum b/go.sum index 26090be..9ca3219 100644 --- a/go.sum +++ b/go.sum @@ -101,6 +101,8 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/karrick/tparse v2.4.2+incompatible h1:+cW306qKAzrASC5XieHkgN7/vPaGKIuK62Q7nI7DIRc= +github.com/karrick/tparse v2.4.2+incompatible/go.mod h1:ASPA+vrIcN1uEW6BZg8vfWbzm69ODPSYZPU6qJyfdK0= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM= @@ -146,6 +148,7 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/openshift/api v3.9.0+incompatible h1:fJ/KsefYuZAjmrr3+5U9yZIZbTOpVkDDLDLFresAeYs= github.com/openshift/api v3.9.1-0.20190322043348-8741ff068a47+incompatible h1:p0ypM7AY7k2VY6ILDPbg3LajGA97hFUt2DGVEQz2Yd4= github.com/openshift/api v3.9.1-0.20190322043348-8741ff068a47+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= github.com/openshift/client-go v0.0.0-20180830153425-431ec9a26e50 h1:y59/+XbTbwzEdS2wveRQTZvjkar7sbVjTNnqFBufr74= diff --git a/pkg/cleanup/cleanup.go b/pkg/cleanup/cleanup.go index a10f28b..62eaa69 100644 --- a/pkg/cleanup/cleanup.go +++ b/pkg/cleanup/cleanup.go @@ -2,8 +2,11 @@ package cleanup import ( "strings" + "regexp" + "time" log "github.com/sirupsen/logrus" + imagev1 "github.com/openshift/api/image/v1" ) // MatchOption type defines how the tags should be matched @@ -62,6 +65,45 @@ func GetInactiveTags(activeTags, tags *[]string) []string { return inactiveTags } +// GetOrphanImageTags returns the tags that do not have any git commit match +func GetOrphanImageTags(gitValues, imageTags *[]string, matchOption MatchOption) []string { + orphans := []string{} + + log.WithField("gitValues", gitValues).Debug("Git commits/tags") + log.WithField("imageTags", imageTags).Debug("Image stream tags") + + for _, tag := range *imageTags { + found := false + for _, value := range *gitValues { + if match(tag, value, matchOption) { + found = true + break + } + } + if !found { + orphans = append(orphans, tag) + } + } + + return orphans +} + +// FilterByRegex returns the tags that match the regexp +func FilterByRegex(imageTags *[]string, regexp *regexp.Regexp) []string { + var matchedTags []string + + log.WithField("pattern:", regexp).Debug("Filtering image tags with regex...") + + for _, tag := range *imageTags { + imageTagMatched := regexp.MatchString(tag) + log.WithField("imageTag:", tag).WithField("match:", imageTagMatched).Debug("Matching image tag") + if imageTagMatched { + matchedTags = append(matchedTags, tag) + } + } + return matchedTags +} + // LimitTags returns the tags which should not be kept by removing the first n tags func LimitTags(tags *[]string, keep int) []string { if len(*tags) > keep { @@ -73,6 +115,26 @@ func LimitTags(tags *[]string, keep int) []string { return []string{} } +// FilterImageTagsByTime returns the tags which are older than the specified time +func FilterImageTagsByTime(imageStreamObjectTags *[]imagev1.NamedTagEventList, olderThan time.Time) []string { + var imageStreamTags []string + + for _, imageStreamTag := range *imageStreamObjectTags { + lastUpdatedDate := imageStreamTag.Items[0].Created.Time + for _, tagEvent := range imageStreamTag.Items { + if lastUpdatedDate.Before(tagEvent.Created.Time) { + lastUpdatedDate = tagEvent.Created.Time + } + } + + if lastUpdatedDate.Before(olderThan) { + imageStreamTags = append(imageStreamTags, imageStreamTag.Tag) + } + } + + return imageStreamTags +} + func match(tag, value string, matchOption MatchOption) bool { switch matchOption { case MatchOptionDefault, MatchOptionPrefix: diff --git a/pkg/cleanup/cleanup_test.go b/pkg/cleanup/cleanup_test.go index 6b766a1..4b7c440 100644 --- a/pkg/cleanup/cleanup_test.go +++ b/pkg/cleanup/cleanup_test.go @@ -1,9 +1,13 @@ package cleanup import ( + "regexp" "testing" + "time" + imagev1 "github.com/openshift/api/image/v1" "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type GetMatchingTagsTestCase struct { @@ -11,6 +15,11 @@ type GetMatchingTagsTestCase struct { matchOption MatchOption } +type GetOrphanTagsTestCase struct { + matchValues, tags, expected []string + matchOption MatchOption +} + type GetInactiveTagsTestCase struct { tags, activeTags, expected []string } @@ -20,6 +29,17 @@ type LimitTagsTestCase struct { limit int } +type FilterByRegexTestCase struct { + tags, expected []string +} + +type TagsOlderThanTestCase struct { + tags []imagev1.NamedTagEventList + expected []string + olderThan time.Time + +} + func Test_GetMatchingTags(t *testing.T) { testcases := []GetMatchingTagsTestCase{ GetMatchingTagsTestCase{ @@ -46,13 +66,83 @@ func Test_GetMatchingTags(t *testing.T) { "c8a693ad89e7069674eda512c553ff56d3ca2ffd-debug", }, }, - GetMatchingTagsTestCase{ + } + + for _, testcase := range testcases { + assert.Equal(t, testcase.expected, GetMatchingTags(&testcase.matchValues, &testcase.tags, testcase.matchOption)) + } +} + +func Test_GetOrphanTags(t *testing.T) { + testcases := []GetOrphanTagsTestCase{ + GetOrphanTagsTestCase{ + matchValues: []string{}, + tags: []string{ + "0b81a958f590ed7ed8be6ec0a2a87816228a482c", + "108f2be974f8e1e5fec8bc759ecf824e81565747", + "4cb7de27c985216b8888ff6049294dae02f3282e", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd", + "4b35e092ad45a626d9a43b7bc7b03e7f7c3c8037", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd-debug", + }, + expected: []string{ + "0b81a958f590ed7ed8be6ec0a2a87816228a482c", + "108f2be974f8e1e5fec8bc759ecf824e81565747", + "4cb7de27c985216b8888ff6049294dae02f3282e", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd", + "4b35e092ad45a626d9a43b7bc7b03e7f7c3c8037", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd-debug", + }, + matchOption: MatchOptionPrefix, + }, + GetOrphanTagsTestCase{ matchValues: []string{ - "v1.0.2", - "2.3", - "1.0", - "v3.1.2", - "v2", + "0b81a958f590ed7ed8be6ec0a2a87816228a482c", + "108f2be974f8e1e5fec8bc759ecf824e81565747", + "4cb7de27c985216b8888ff6049294dae02f3282e", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd", + "4b35e092ad45a626d9a43b7bc7b03e7f7c3c8037", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd-debug", + }, + tags: []string{ + "0b81a958f590ed7ed8be6ec0a2a87816228a482c", + "108f2be974f8e1e5fec8bc759ecf824e81565747", + "4cb7de27c985216b8888ff6049294dae02f3282e", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd", + "4b35e092ad45a626d9a43b7bc7b03e7f7c3c8037", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd-debug", + }, + expected: []string{}, + matchOption: MatchOptionPrefix, + }, + GetOrphanTagsTestCase{ + matchValues: []string{ + "0b81a958f590ed7ed8be6ec0a2a87816228a482c", + "108f2be974f8e1e5fec8bc759ecf824e81565747", + "4cb7de27c985216b8888ff6049294dae02f3282e", + "4b35e092ad45a626d9a43b7bc7b03e7f7c3c8037", + + }, + tags: []string{ + "0b81a958f590ed7ed8be6ec0a2a87816228a482c", + "108f2be974f8e1e5fec8bc759ecf824e81565747", + "4cb7de27c985216b8888ff6049294dae02f3282e", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd", + "4b35e092ad45a626d9a43b7bc7b03e7f7c3c8037", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd-debug", + }, + expected: []string{ + "c8a693ad89e7069674eda512c553ff56d3ca2ffd", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd-debug", + }, + matchOption: MatchOptionPrefix, + }, + GetOrphanTagsTestCase{ + matchValues: []string{ + "3.4", + "0.0.1", + "0.0.2", + "v2.3.0", }, tags: []string{ "1.0", @@ -62,16 +152,16 @@ func Test_GetMatchingTags(t *testing.T) { "0.0.2", "v2.3.0", }, - matchOption: MatchOptionExact, expected: []string{ - "v1.0.2", "1.0", + "v1.0.2", }, + matchOption: MatchOptionExact, }, } for _, testcase := range testcases { - assert.Equal(t, testcase.expected, GetMatchingTags(&testcase.matchValues, &testcase.tags, testcase.matchOption)) + assert.Equal(t, testcase.expected, GetOrphanImageTags(&testcase.matchValues, &testcase.tags, testcase.matchOption)) } } @@ -106,6 +196,36 @@ func Test_GetInactiveTags(t *testing.T) { } } +func Test_FilterByRegex(t *testing.T) { + reg, err := regexp.Compile("^[a-z0-9]{40}$") + testcases := []FilterByRegexTestCase{ + FilterByRegexTestCase{ + tags: []string{ + "0b81a958f590ed7ed8be6ec0a2a87816228a482c", + "108f2be974f8e1e5fec8bc759ecf824e81565747", + "4cb7de27c985216b8888ff6049294dae02f3282e", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd", + "4b35e092ad45a626d9a43b7bc7b03e7f7c3c8037", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd-debug", + "v2.0", + "v2.0-4", + }, + expected: []string{ + "0b81a958f590ed7ed8be6ec0a2a87816228a482c", + "108f2be974f8e1e5fec8bc759ecf824e81565747", + "4cb7de27c985216b8888ff6049294dae02f3282e", + "c8a693ad89e7069674eda512c553ff56d3ca2ffd", + "4b35e092ad45a626d9a43b7bc7b03e7f7c3c8037", + }, + }, + } + + assert.NoError(t, err) + for _, testcase := range testcases { + assert.Equal(t, testcase.expected, FilterByRegex(&testcase.tags, reg)) + } +} + func Test_LimitTags(t *testing.T) { testcases := []LimitTagsTestCase{ LimitTagsTestCase{ @@ -143,3 +263,65 @@ func Test_LimitTags(t *testing.T) { assert.Equal(t, testcase.expected, LimitTags(&testcase.tags, testcase.limit)) } } + +func Test_TagsOlderThan(t *testing.T) { + testcases := []TagsOlderThanTestCase{ + TagsOlderThanTestCase{ + tags: []imagev1.NamedTagEventList{ + imagev1.NamedTagEventList{ + Tag: "0b81a958f590ed7ed8be6ec0a2a87816228a482c", + Items: []imagev1.TagEvent { + imagev1.TagEvent{ + Created: metav1.Time{ + Time: time.Date(2020, 1, 1,0,0,0,0,time.Local), + }, + }, + imagev1.TagEvent{ + Created: metav1.Time{ + Time: time.Date(2020, 5, 5,0,0,0,0,time.Local), + }, + }, + }, + }, + imagev1.NamedTagEventList{ + Tag: "108f2be974f8e1e5fec8bc759ecf824e81565747", + Items: []imagev1.TagEvent { + imagev1.TagEvent{ + Created: metav1.Time{ + Time: time.Date(2020, 4, 4,0,0,0,0,time.Local), + }, + }, + }, + }, + imagev1.NamedTagEventList{ + Tag: "4cb7de27c985216b8888ff6049294dae02f3282e", + Items: []imagev1.TagEvent { + imagev1.TagEvent{ + Created: metav1.Time{ + Time: time.Date(2020, 3, 3,0,0,0,0,time.Local), + }, + }, + }, + }, + imagev1.NamedTagEventList{ + Tag: "c8a693ad89e7069674eda512c553ff56d3ca2ffd-debug", + Items: []imagev1.TagEvent { + imagev1.TagEvent{ + Created: metav1.Time{ + Time: time.Date(2020, 2, 2,0,0,0,0,time.Local), + }, + }, + }, + }, + }, + expected: []string{ + "c8a693ad89e7069674eda512c553ff56d3ca2ffd-debug", + }, + olderThan: time.Date(2020, 3, 3,0,0,0,0,time.Local), + }, + } + + for _, testcase := range testcases { + assert.Equal(t, testcase.expected, FilterImageTagsByTime(&testcase.tags, testcase.olderThan)) + } +} diff --git a/pkg/git/git.go b/pkg/git/git.go index 2fc1779..fa57213 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -7,7 +7,7 @@ import ( "gopkg.in/src-d/go-git.v4" ) -// GetCommitHashes returns the commit hashes of a given repository ordered by the `git.LogOrderCommitterTime`. If `commitLimit` is -1 all commits will be returned. +// GetCommitHashes returns the commit hashes of a given repository ordered by the `git.LogOrderCommitterTime`. If `commitLimit` is 0 all commits will be returned. func GetCommitHashes(repoPath string, commitLimit int) ([]string, error) { var commitHashes []string @@ -22,7 +22,7 @@ func GetCommitHashes(repoPath string, commitLimit int) ([]string, error) { return nil, err } - for i := 0; i < commitLimit || commitLimit < 0; i++ { + for i := 0; i < commitLimit || commitLimit == 0; i++ { commit, err := commitIter.Next() if err != nil { if err == io.EOF { @@ -36,7 +36,7 @@ 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. +// GetTags returns the commit tags of a given repository ordered alphabetically or by version. If `commitLimit` is 0 all tags will be returned. func GetTags(repoPath string, tagLimit int, sortTagBy SortOption) ([]string, error) { var commitTags []string @@ -51,7 +51,7 @@ func GetTags(repoPath string, tagLimit int, sortTagBy SortOption) ([]string, err return nil, err } - for i := 0; i < tagLimit || tagLimit < 0; i++ { + for i := 0; i < tagLimit || tagLimit == 0; i++ { tag, err := tagIter.Next() if err != nil { diff --git a/pkg/git/git_test.go b/pkg/git/git_test.go index 33e2e47..b5a5f0b 100644 --- a/pkg/git/git_test.go +++ b/pkg/git/git_test.go @@ -29,25 +29,21 @@ func Test_GetCommitHashesFail(t *testing.T) { } func Test_GetTagsSortedInAlphabeticalOrder(t *testing.T) { - commitLimit := 2 - commitHashes, err := GetTags("../..", commitLimit, "alphabetic") // Open repository from root dir + commitHashes, err := sortTags([]string{ "v0.1.0", "2.0", "0.0.1"}, SortOptionAlphabetic) - expectInOrder := []string{"0.0.1", "v0.1.0"} + expectInOrder := []string{"0.0.1", "2.0", "v0.1.0"} assert.NoError(t, err) - assert.Len(t, commitHashes, commitLimit) - assert.EqualValues(t, commitHashes, expectInOrder) + assert.EqualValues(t, expectInOrder, commitHashes) } func Test_GetTagsSortedByVersion(t *testing.T) { - commitLimit := 2 - commitHashes, err := GetTags("../..", commitLimit, "version") // Open repository from root dir + commitHashes, err := sortTags([]string{"0.0.5", "v0.1.0", "0.0.2", "v0.0.1"}, SortOptionVersion) // Open repository from root dir - expectInOrder := []string{"v0.1.0", "0.0.1"} + expectInOrder := []string{ "v0.1.0", "0.0.5", "0.0.2", "v0.0.1"} assert.NoError(t, err) - assert.Len(t, commitHashes, commitLimit) - assert.EqualValues(t, commitHashes, expectInOrder) + assert.EqualValues(t, expectInOrder, commitHashes) } func Test_GetAllTags(t *testing.T) { diff --git a/pkg/openshift/imagestream.go b/pkg/openshift/imagestream.go index b934215..608e6a9 100644 --- a/pkg/openshift/imagestream.go +++ b/pkg/openshift/imagestream.go @@ -4,6 +4,7 @@ import ( "github.com/appuio/image-cleanup/pkg/kubernetes" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + imagev1 "github.com/openshift/api/image/v1" ) var ( @@ -63,9 +64,8 @@ func GetImageStreams(namespace string) ([]string, error) { return imageStreams, nil } -// GetImageStreamTags returns the tags of an image stream -func GetImageStreamTags(namespace, imageStreamName string) ([]string, error) { - var imageStreamTags []string +// GetImageStreamTags returns the tags of an image stream older than the specified time +func GetImageStreamTags(namespace, imageStreamName string) ([]imagev1.NamedTagEventList, error) { imageClient, err := NewImageV1Client() if err != nil { @@ -77,13 +77,7 @@ func GetImageStreamTags(namespace, imageStreamName string) ([]string, error) { return nil, err } - imageStreamTags = make([]string, len(imageStream.Status.Tags)) - - for i, imageStreamTag := range imageStream.Status.Tags { - imageStreamTags[i] = imageStreamTag.Tag - } - - return imageStreamTags, nil + return imageStream.Status.Tags, nil } // DeleteImageStreamTag deletes the image stream tag