Skip to content

Commit

Permalink
Adopt cobra best practices for setting up commands
Browse files Browse the repository at this point in the history
  • Loading branch information
ccremer committed Mar 6, 2020
1 parent e18fb03 commit 812bf4a
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 139 deletions.
23 changes: 3 additions & 20 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"fmt"
"github.com/appuio/image-cleanup/pkg/git"
"github.com/appuio/image-cleanup/pkg/openshift"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand All @@ -20,6 +19,9 @@ type (
SortCriteria string
}
)
var (
gitOptions = GitOptions{}
)

func DeleteImages(imageTags []string, imageName string, namespace string) {
for _, inactiveTag := range imageTags {
Expand All @@ -46,22 +48,3 @@ func PrintImageTags(cmd *cobra.Command, imageTags []string) {
}
}

func getGitCandidateList(o *GitOptions) []string {
logEvent := log.WithFields(log.Fields{
"GitRepoPath": o.RepoPath,
"CommitLimit": o.CommitLimit,
})
if o.Tag {
candidates, err := git.GetTags(o.RepoPath, o.CommitLimit, git.SortOption(o.SortCriteria))
if err != nil {
logEvent.WithError(err).Fatal("Retrieving commit tags failed.")
}
return candidates
} else {
candidates, err := git.GetCommitHashes(o.RepoPath, o.CommitLimit)
if err != nil {
logEvent.WithError(err).Fatal("Retrieving commit hashes failed.")
}
return candidates
}
}
70 changes: 38 additions & 32 deletions cmd/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,44 +14,48 @@ type HistoryCleanupOptions struct {
Force bool
Keep int
ImageRepository string
Namespace string
}

// NewHistoryCleanupCommand creates a cobra command to clean up images by comparing the git commit history.
func NewHistoryCleanupCommand() *cobra.Command {
historyCleanupOptions := HistoryCleanupOptions{}
gitOptions := GitOptions{}
cmd := &cobra.Command{
var (
historyCleanupOptions = HistoryCleanupOptions{}
historyCmd = &cobra.Command{
Use: "history",
Aliases: []string{"hist"},
Short: "Clean up excessive image tags",
Long: `Clean up excessive image tags matching the commit hashes (prefix) of the git repository`,
Run: func(cmd *cobra.Command, args []string) {
validateFlagCombinationInput(&gitOptions)
ExecuteHistoryCleanupCommand(cmd, &historyCleanupOptions, &gitOptions, args)
validateFlagCombinationInput()
ExecuteHistoryCleanupCommand(cmd, args)
},
}
cmd.Flags().BoolVarP(&historyCleanupOptions.Force, "force", "f", false, "Confirm deletion of image tags.")
cmd.Flags().IntVarP(&gitOptions.CommitLimit, "git-commit-limit", "l", 0,
)

func init() {
rootCmd.AddCommand(historyCmd)
historyCmd.Flags().BoolVarP(&historyCleanupOptions.Force, "force", "f", false, "Confirm deletion of image tags.")
historyCmd.Flags().IntVarP(&gitOptions.CommitLimit, "git-commit-limit", "l", 0,
"Only look at the first <l> commits to compare with image tags. Use 0 (zero) for all commits. Limited effect if repo is a shallow clone.")
cmd.Flags().StringVarP(&gitOptions.RepoPath, "git-repo-path", "p", ".", "Path to Git repository.")
cmd.Flags().StringVarP(&historyCleanupOptions.ImageRepository, imageRepositoryCliFlag, "i", "", "Image repository in form of <namespace/repo>.")
cmd.Flags().IntVarP(&historyCleanupOptions.Keep, "keep", "k", 10, "Keep most current <k> images.")
cmd.Flags().BoolVarP(&gitOptions.Tag, "tags", "t", false, "Compare with Git tags instead of commit hashes.")
cmd.Flags().StringVar(&gitOptions.SortCriteria, "sort", string(git.SortOptionVersion),
historyCmd.Flags().StringVarP(&gitOptions.RepoPath, "git-repo-path", "p", ".", "Path to Git repository.")
historyCmd.Flags().StringVarP(&historyCleanupOptions.ImageRepository, imageRepositoryCliFlag, "i", "", "Image repository in form of <namespace/repo>.")
historyCmd.Flags().IntVarP(&historyCleanupOptions.Keep, "keep", "k", 10, "Keep most current <k> images.")
historyCmd.Flags().BoolVarP(&gitOptions.Tag, "tags", "t", false, "Compare with Git tags instead of commit hashes.")
historyCmd.Flags().StringVar(&gitOptions.SortCriteria, "sort", string(git.SortOptionVersion),
fmt.Sprintf("Sort git tags by criteria. Only effective with --tags. Allowed values: %s", []git.SortOption{git.SortOptionVersion, git.SortOptionAlphabetic}))
cmd.MarkFlagRequired("image-repository")
return cmd
historyCmd.MarkFlagRequired("image-repository")

}

func ExecuteHistoryCleanupCommand(cmd *cobra.Command, o *HistoryCleanupOptions, gitOptions *GitOptions, args []string) {
func ExecuteHistoryCleanupCommand(cmd *cobra.Command, args []string) {

imageStreamObjectTags, err := openshift.GetImageStreamTags(o.Namespace, o.ImageRepository)
namespace, image, _ := splitNamespaceAndImagestream(historyCleanupOptions.ImageRepository)

imageStreamObjectTags, err := openshift.GetImageStreamTags(namespace, image)
if err != nil {
log.WithError(err).
WithFields(log.Fields{
"ImageRepository": o.Namespace,
"ImageName": o.ImageRepository}).
"ImageRepository": namespace,
"ImageName": image,
}).
Fatal("Could not retrieve image stream.")
}

Expand All @@ -60,41 +64,43 @@ func ExecuteHistoryCleanupCommand(cmd *cobra.Command, o *HistoryCleanupOptions,
imageStreamTags = append(imageStreamTags, imageTag.Tag)
}

var matchOption cleanup.MatchOption
matchOption := cleanup.MatchOptionDefault
if gitOptions.Tag {
matchOption = cleanup.MatchOptionExact
}

gitCandidates := getGitCandidateList(gitOptions)
gitCandidates := git.GetGitCandidateList(&gitOptions)
var matchingTags = cleanup.GetMatchingTags(&gitCandidates, &imageStreamTags, matchOption)

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

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

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

PrintImageTags(cmd, inactiveTags)

if o.Force {
DeleteImages(inactiveTags, o.ImageRepository, o.Namespace)
if historyCleanupOptions.Force {
DeleteImages(inactiveTags, image, namespace)
} else {
log.Info("--force was not specified. Nothing has been deleted.")
}
}

func validateFlagCombinationInput(gitOptions *GitOptions) {
func validateFlagCombinationInput() {

if gitOptions.Tag && !git.IsValidSortValue(gitOptions.SortCriteria) {
log.WithField("sort_criteria", gitOptions.SortCriteria).Fatal("Invalid sort criteria.")
log.WithFields(log.Fields{
"error": "invalid sort criteria",
"sort": gitOptions.SortCriteria,
}).Fatal("Could not parse sort criteria.")
}

}
74 changes: 39 additions & 35 deletions cmd/orphan.go → cmd/orphans.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,55 +29,59 @@ This command deletes images that are not found in the git history.`
orphanOlderThanCliFlag = "older-than"
)

// NewOrphanCleanupCommand creates a cobra command to clean up images by comparing the git commit history. It removes any
// image tags that are not found in the git history by given criteria.
func NewOrphanCleanupCommand() *cobra.Command {
o := OrphanCleanupOptions{}
gitOptions := GitOptions{}
cmd := &cobra.Command{
var (
orphanCleanupOptions = OrphanCleanupOptions{}
// orphanCmd represents a cobra command to clean up images by comparing the git commit history. It removes any
// image tags that are not found in the git history by given criteria.
orphanCmd = &cobra.Command{
Use: "orphans",
Aliases: []string{"orph"},
Short: "Clean up unknown image tags",
Long: orphanCommandLongDescription,
Aliases: []string{"orph"},
RunE: func(cmd *cobra.Command, args []string) error {
validateOrphanCommandInput(&o, &gitOptions)
return ExecuteOrphanCleanupCommand(cmd, &o, args)
validateOrphanCommandInput()
return ExecuteOrphanCleanupCommand(cmd, args)
},
}
cmd.Flags().BoolVarP(&o.Force, "force", "f", false, "Confirm deletion of image tags.")
cmd.Flags().IntVarP(&gitOptions.CommitLimit, "git-commit-limit", "l", 0,
)

func init() {
rootCmd.AddCommand(orphanCmd)

orphanCmd.Flags().BoolVarP(&orphanCleanupOptions.Force, "force", "f", false, "Confirm deletion of image tags.")
orphanCmd.Flags().IntVarP(&gitOptions.CommitLimit, "git-commit-limit", "l", 0,
"Only look at the first <l> commits to compare with tags. Use 0 (zero) for all commits. Limited effect if repo is a shallow clone.")
cmd.Flags().StringVarP(&gitOptions.RepoPath, "git-repo-path", "p", ".", "Path to Git repository")
cmd.Flags().StringVarP(&o.ImageRepository, imageRepositoryCliFlag, "i", "", "Image repository (e.g. namespace/repo)")
cmd.Flags().BoolVarP(&gitOptions.Tag, "tags", "t", false,
orphanCmd.Flags().StringVarP(&gitOptions.RepoPath, "git-repo-path", "p", ".", "Path to Git repository")
orphanCmd.Flags().StringVarP(&orphanCleanupOptions.ImageRepository, imageRepositoryCliFlag, "i", "", "Image repository (e.g. namespace/repo)")
orphanCmd.Flags().BoolVarP(&gitOptions.Tag, "tags", "t", false,
"Instead of comparing commit history, it will compare git tags with the existing image tags, removing any image tags that do not match")
cmd.Flags().StringVar(&gitOptions.SortCriteria, "sort", string(git.SortOptionVersion),
orphanCmd.Flags().StringVar(&gitOptions.SortCriteria, "sort", string(git.SortOptionVersion),
fmt.Sprintf("Sort git tags by criteria. Only effective with --tags. Allowed values: [%s, %s]", git.SortOptionVersion, git.SortOptionAlphabetic))
cmd.Flags().StringVar(&o.OlderThan, orphanOlderThanCliFlag, "2mo",
"delete images that are older than the duration. Ex.: [1y2mo3w4d5h6m7s]")
cmd.Flags().StringVarP(&o.OrphanDeletionRegex, orphanDeletionPatternCliFlag, "r", "^[a-z0-9]{40}$",
orphanCmd.Flags().StringVar(&orphanCleanupOptions.OlderThan, orphanOlderThanCliFlag, "2mo",
"Delete images that are older than the duration. Ex.: [1y2mo3w4d5h6m7s]")
orphanCmd.Flags().StringVarP(&orphanCleanupOptions.OrphanDeletionRegex, orphanDeletionPatternCliFlag, "r", "^[a-z0-9]{40}$",
"Delete images that match the regex, defaults to matching Git SHA commits")
cmd.MarkFlagRequired("image-repository")
return cmd
orphanCmd.MarkFlagRequired("image-repository")

}

func validateOrphanCommandInput(o *OrphanCleanupOptions, gitOptions *GitOptions) {
func validateOrphanCommandInput() {

if _, _, err := splitNamespaceAndImagestream(o.ImageRepository); err != nil {
if _, _, err := splitNamespaceAndImagestream(orphanCleanupOptions.ImageRepository); err != nil {
log.WithError(err).
WithField(imageRepositoryCliFlag, o.ImageRepository).
WithField(imageRepositoryCliFlag, orphanCleanupOptions.ImageRepository).
Fatal("Could not parse image repository.")
}

if _, err := parseOrphanDeletionRegex(o.OrphanDeletionRegex); err != nil {
if _, err := parseOrphanDeletionRegex(orphanCleanupOptions.OrphanDeletionRegex); err != nil {
log.WithError(err).
WithField(orphanDeletionPatternCliFlag, o.OrphanDeletionRegex).
WithField(orphanDeletionPatternCliFlag, orphanCleanupOptions.OrphanDeletionRegex).
Fatal("Could not parse orphan deletion pattern.")
}

if _, err := parseCutOffDateTime(o.OlderThan); err != nil {
if _, err := parseCutOffDateTime(orphanCleanupOptions.OlderThan); err != nil {
log.WithError(err).
WithField(orphanOlderThanCliFlag, o.OlderThan).
WithField(orphanOlderThanCliFlag, orphanCleanupOptions.OlderThan).
Fatal("Could not parse cut off date.")
}

Expand All @@ -90,30 +94,30 @@ func validateOrphanCommandInput(o *OrphanCleanupOptions, gitOptions *GitOptions)

}

func ExecuteOrphanCleanupCommand(cmd *cobra.Command, o *OrphanCleanupOptions, gitOptions *GitOptions, args []string) error {
func ExecuteOrphanCleanupCommand(cmd *cobra.Command, args []string) error {

gitCandidates := getGitCandidateList(gitOptions)
gitCandidates := git.GetGitCandidateList(&gitOptions)

namespace, imageName, err := splitNamespaceAndImagestream(o.ImageRepository)
namespace, imageName, _ := splitNamespaceAndImagestream(orphanCleanupOptions.ImageRepository)

imageStreamObjectTags, err := openshift.GetImageStreamTags(namespace, imageName)
if err != nil {
log.WithError(err).
WithFields(log.Fields{
"ImageRepository": o.ImageRepository,
"ImageRepository": orphanCleanupOptions.ImageRepository,
}).
Fatal("Could not retrieve image stream.")
}

cutOffDateTime, _ := parseCutOffDateTime(o.OlderThan)
cutOffDateTime, _ := parseCutOffDateTime(orphanCleanupOptions.OlderThan)
imageStreamTags := cleanup.FilterImageTagsByTime(&imageStreamObjectTags, cutOffDateTime)

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

orphanIncludeRegex, _ := parseOrphanDeletionRegex(o.OrphanDeletionRegex)
orphanIncludeRegex, _ := parseOrphanDeletionRegex(orphanCleanupOptions.OrphanDeletionRegex)
var matchingTags []string
matchingTags = cleanup.GetOrphanImageTags(&gitCandidates, &imageStreamTags, matchOption)
matchingTags = cleanup.FilterByRegex(&imageStreamTags, orphanIncludeRegex)
Expand All @@ -122,7 +126,7 @@ func ExecuteOrphanCleanupCommand(cmd *cobra.Command, o *OrphanCleanupOptions, gi
if err != nil {
log.WithError(err).
WithFields(log.Fields{
"ImageRepository": o.ImageRepository,
"ImageRepository": orphanCleanupOptions.ImageRepository,
"ImageName": imageName,
"imageStreamTags": imageStreamTags}).
Fatal("Could not retrieve active image stream tags.")
Expand All @@ -133,7 +137,7 @@ func ExecuteOrphanCleanupCommand(cmd *cobra.Command, o *OrphanCleanupOptions, gi

PrintImageTags(cmd, inactiveImageTags)

if o.Force {
if orphanCleanupOptions.Force {
DeleteImages(inactiveImageTags, imageName, namespace)
} else {
log.Info("--force was not specified. Nothing has been deleted.")
Expand Down
File renamed without changes.
74 changes: 74 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package cmd

import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"io/ioutil"
"os"
)

// Options is a struct holding the options of the root command
type (
Options struct {
LogLevel string
Batch bool
Verbose bool
}
)

var (
// rootCmd represents the base command when called without any subcommands
rootCmd = &cobra.Command{
Use: "image-cleanup",
Short: "Cleans up images tags on remote registries",
}
options = &Options{}
)

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

func init() {
rootCmd.PersistentFlags().StringVar(&options.LogLevel, "logLevel", "info", "Log level to use")
rootCmd.PersistentFlags().BoolVarP(&options.Verbose, "verbose", "v", false, "Shorthand for --logLevel debug")
rootCmd.PersistentFlags().BoolVarP(&options.Batch, "batch", "b", false, "Use Batch mode (disables logging, prints deleted images only)")
cobra.OnInitialize(initConfig)

}

// initConfig reads in config file and ENV variables if set.
func initConfig() {

log.SetFormatter(&log.TextFormatter{
FullTimestamp: true,
})

if options.Batch {
log.SetOutput(ioutil.Discard)
} else {
log.SetOutput(os.Stderr)
}
if options.Verbose {
log.SetLevel(log.DebugLevel)
} else {
level, err := log.ParseLevel(options.LogLevel)
if err != nil {
log.WithField("error", err).Warn("Using info level.")
log.SetLevel(log.InfoLevel)
} else {
log.SetLevel(level)
}
}
}

// SetVersion sets the version string in the help messages
func SetVersion(version string) {
rootCmd.Version = version
}
Loading

0 comments on commit 812bf4a

Please sign in to comment.