diff --git a/Makefile b/Makefile index baa98dbe..6432cba1 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ lint: ## Runs linter .PHONY: vet vet: ## Runs go vet - @go vet ${PKGS} + @go vet -composites=false ${PKGS} .PHONY: clean clean: ## Removes build artifacts @@ -43,4 +43,3 @@ bin/ctl: ## Make a link to the executable for this OS type for convenience .PHONY: test test: ## Runs go tests on all subdirs @go test -coverprofile coverage.txt -covermode=atomic ./... - diff --git a/cmd/describe.go b/cmd/describe.go index e47e5ff8..b0adab5a 100644 --- a/cmd/describe.go +++ b/cmd/describe.go @@ -2,12 +2,12 @@ package cmd import ( "errors" + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" ) -func GetDescribeCmd(c *client.Client) *cobra.Command { +func describeCmd(c *client.Client) *cobra.Command { return &cobra.Command{ Use: "describe pods [flags]", Short: "Show details of a specific pod(s)", @@ -28,10 +28,9 @@ If context(s) not specified, it will search through all contexts.`, return err } if len(pods) == 0 { - return errors.New("Could not find any matching pods!") - } else { - describePodList(pods) + return errors.New("could not find any matching pods") } + describePodList(pods) return nil }, } diff --git a/cmd/describe_test.go b/cmd/describe_test.go index 2b950031..615a7821 100644 --- a/cmd/describe_test.go +++ b/cmd/describe_test.go @@ -22,7 +22,7 @@ func TestDescribeSingle(t *testing.T) { cl := client.GetFakeConfigClient(map[string][]runtime.Object{"hi": []runtime.Object{pod.DeepCopyObject()}}) - cmd := GetDescribeCmd(cl) + cmd := describeCmd(cl) cmd.Flags().StringSliceP("context", "x", nil, "Context") cmd.SetArgs([]string{"test"}) @@ -47,7 +47,7 @@ func TestDescribeBadContext(t *testing.T) { cl := client.GetFakeConfigClient(map[string][]runtime.Object{"hi": []runtime.Object{pod.DeepCopyObject()}}) - cmd := GetDescribeCmd(cl) + cmd := describeCmd(cl) cmd.Flags().StringSliceP("context", "x", nil, "Context") cmd.SetArgs([]string{"test", "--context=wow"}) @@ -74,7 +74,7 @@ func TestDescribeUnfound(t *testing.T) { cl := client.GetFakeConfigClient(map[string][]runtime.Object{"hi": []runtime.Object{pod.DeepCopyObject()}}) - cmd := GetDescribeCmd(cl) + cmd := describeCmd(cl) cmd.Flags().StringSliceP("context", "x", nil, "Context") cmd.SetArgs([]string{"pew"}) diff --git a/cmd/get.go b/cmd/get.go index 1b392cd9..e12276e0 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -1,12 +1,12 @@ package cmd import ( + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" ) -func GetGetCmd(c *client.Client) *cobra.Command { +func getCmd(c *client.Client) *cobra.Command { return &cobra.Command{ Use: "get [flags]", Short: "Get a list of pods", diff --git a/cmd/kron/common.go b/cmd/kron/common.go index 8042d8fc..26b9dadd 100644 --- a/cmd/kron/common.go +++ b/cmd/kron/common.go @@ -2,9 +2,9 @@ package kron import ( "fmt" - "github.com/wish/ctl/pkg/client/types" "github.com/robfig/cron" "github.com/spf13/viper" + "github.com/wish/ctl/pkg/client/types" "os" "time" ) diff --git a/cmd/kron/describe.go b/cmd/kron/describe.go index 12888c85..884671d1 100644 --- a/cmd/kron/describe.go +++ b/cmd/kron/describe.go @@ -1,15 +1,15 @@ package kron import ( + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" "github.com/wish/ctl/pkg/client/types" - "github.com/spf13/cobra" ) // Currently does not support selected job // Requires job name -func GetDescribeCmd(c *client.Client) *cobra.Command { +func describeCmd(c *client.Client) *cobra.Command { cmd := &cobra.Command{ Use: "describe [jobs] [flags]", Short: "Show details about specified cron jobs", diff --git a/cmd/kron/exec.go b/cmd/kron/exec.go index 2938e470..1ee80eaa 100644 --- a/cmd/kron/exec.go +++ b/cmd/kron/exec.go @@ -1,12 +1,12 @@ package kron import ( + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" ) -func GetExecCmd(c *client.Client) *cobra.Command { +func execCmd(c *client.Client) *cobra.Command { return &cobra.Command{ Use: "exec cronjob [flags]", Short: "Executes a job now", diff --git a/cmd/kron/favorite.go b/cmd/kron/favorite.go index 5aaeab4e..b91bdf58 100644 --- a/cmd/kron/favorite.go +++ b/cmd/kron/favorite.go @@ -1,31 +1,28 @@ package kron import ( - "fmt" - "github.com/wish/ctl/pkg/client" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/wish/ctl/pkg/client" ) -func init() { - viper.SetDefault("favorites", make(map[string]location)) - viper.SetConfigName("config") - viper.AddConfigPath("$HOME/.kron") - err := viper.ReadInConfig() - if err != nil { - // Write config file - fmt.Println("Creating new config file") - createConfig() - // panic(err.Error()) - } -} - -func GetFavoriteCmd(c *client.Client) *cobra.Command { +func favoriteCmd(c *client.Client) *cobra.Command { return &cobra.Command{ Use: "favorite [jobs] [flags]", Short: "Adds jobs to favorite list", Long: `Adds specified job(s) to the favorite list. If no job was specified the selected job is added. A namespace and contexts can be specified to limit matches.`, + PreRun: func(cmd *cobra.Command, args []string) { + viper.SetDefault("favorites", make(map[string]location)) + viper.SetConfigName("config") + viper.AddConfigPath("$HOME/.kron") + err := viper.ReadInConfig() + if err != nil { + // Write config file + cmd.Println("Creating new config file") + createConfig() + } + }, RunE: func(cmd *cobra.Command, args []string) error { // args/flags ctxs, _ := cmd.Flags().GetStringSlice("context") diff --git a/cmd/kron/format.go b/cmd/kron/format.go index a3416c20..2e0d53ab 100644 --- a/cmd/kron/format.go +++ b/cmd/kron/format.go @@ -2,8 +2,8 @@ package kron import ( "fmt" - "github.com/wish/ctl/pkg/client/types" "github.com/robfig/cron" + "github.com/wish/ctl/pkg/client/types" "os" "text/tabwriter" "time" diff --git a/cmd/kron/get.go b/cmd/kron/get.go index 627fbfab..96960e6a 100644 --- a/cmd/kron/get.go +++ b/cmd/kron/get.go @@ -2,13 +2,13 @@ package kron import ( "errors" + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" "sort" ) -func GetGetCmd(c *client.Client) *cobra.Command { +func getCmd(c *client.Client) *cobra.Command { cmd := &cobra.Command{ Use: "get [flags]", Short: "Get a list of cronjobs", @@ -31,7 +31,7 @@ If context(s) not specified, it will list from all contexts.`, E, _ := cmd.Flags().GetBool("by-next-run-reverse") if l && L || l && e || l && E || L && e || L && E || e && E { // More than one - return errors.New("Only at most one ordering flag may be set!") + return errors.New("at most one ordering flag may be set") } list, err := c.ListCronJobsOverContexts(ctxs, namespace, options) diff --git a/cmd/kron/root.go b/cmd/kron/root.go index d5eb22fa..9bdb96c6 100644 --- a/cmd/kron/root.go +++ b/cmd/kron/root.go @@ -1,28 +1,29 @@ package kron import ( + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/kron/runs" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" ) -func GetKronCmd(c *client.Client) *cobra.Command { +// Cmd returns the kron subcommand given a client to operate on +func Cmd(c *client.Client) *cobra.Command { kron := &cobra.Command{ Use: "kron", Short: "A tool for cron on kubernetes", Long: "A subcommand for managing and reviewing cron jobs on kubernetes.", } - kron.AddCommand(GetDescribeCmd(c)) - kron.AddCommand(GetExecCmd(c)) - kron.AddCommand(GetFavoriteCmd(c)) - kron.AddCommand(GetGetCmd(c)) - kron.AddCommand(GetSelectCmd(c)) - kron.AddCommand(GetSuspendCmd(c)) - kron.AddCommand(GetUnfavoriteCmd(c)) - kron.AddCommand(GetUnsuspendCmd(c)) - kron.AddCommand(GetWebCmd(c)) - kron.AddCommand(runs.GetRunsCmd(c)) + kron.AddCommand(describeCmd(c)) + kron.AddCommand(execCmd(c)) + kron.AddCommand(favoriteCmd(c)) + kron.AddCommand(getCmd(c)) + kron.AddCommand(selectCmd(c)) + kron.AddCommand(suspendCmd(c)) + kron.AddCommand(unfavoriteCmd(c)) + kron.AddCommand(unsuspendCmd(c)) + kron.AddCommand(webCmd(c)) + kron.AddCommand(runs.Cmd(c)) return kron } diff --git a/cmd/kron/runs/describe.go b/cmd/kron/runs/describe.go index abb48b2f..0bb42c48 100644 --- a/cmd/kron/runs/describe.go +++ b/cmd/kron/runs/describe.go @@ -1,12 +1,12 @@ package runs import ( + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" ) -func GetDescribeCmd(c *client.Client) *cobra.Command { +func describeCmd(c *client.Client) *cobra.Command { return &cobra.Command{ Use: "describe run", Short: "Get info about a run", diff --git a/cmd/kron/runs/get.go b/cmd/kron/runs/get.go index e2e40847..56b255cd 100644 --- a/cmd/kron/runs/get.go +++ b/cmd/kron/runs/get.go @@ -1,12 +1,12 @@ package runs import ( + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" ) -func GetGetCmd(c *client.Client) *cobra.Command { +func getCmd(c *client.Client) *cobra.Command { return &cobra.Command{ Use: "get cronjob [flags]", Short: "Get a list of runs of a cron job", diff --git a/cmd/kron/runs/logs.go b/cmd/kron/runs/logs.go index 1cc93d74..f03baa35 100644 --- a/cmd/kron/runs/logs.go +++ b/cmd/kron/runs/logs.go @@ -1,12 +1,12 @@ package runs import ( + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" ) -func GetLogsCmd(c *client.Client) *cobra.Command { +func logsCmd(c *client.Client) *cobra.Command { cmd := &cobra.Command{ Use: "logs pod [flags]", Aliases: []string{"log"}, diff --git a/cmd/kron/runs/root.go b/cmd/kron/runs/root.go index e3bc15d7..78bbc120 100644 --- a/cmd/kron/runs/root.go +++ b/cmd/kron/runs/root.go @@ -1,11 +1,12 @@ package runs import ( - "github.com/wish/ctl/pkg/client" "github.com/spf13/cobra" + "github.com/wish/ctl/pkg/client" ) -func GetRunsCmd(c *client.Client) *cobra.Command { +// Cmd returns the kron/runs subcommand given a client +func Cmd(c *client.Client) *cobra.Command { cmd := &cobra.Command{ Use: "runs", Short: "Subcommand on recent runs of a cron job", @@ -13,9 +14,9 @@ func GetRunsCmd(c *client.Client) *cobra.Command { Has a bunch of subcommand just like kron`, } - cmd.AddCommand(GetDescribeCmd(c)) - cmd.AddCommand(GetGetCmd(c)) - cmd.AddCommand(GetLogsCmd(c)) + cmd.AddCommand(describeCmd(c)) + cmd.AddCommand(getCmd(c)) + cmd.AddCommand(logsCmd(c)) return cmd } diff --git a/cmd/kron/select.go b/cmd/kron/select.go index ef850dbb..598064d5 100644 --- a/cmd/kron/select.go +++ b/cmd/kron/select.go @@ -1,25 +1,12 @@ package kron import ( - "fmt" - "github.com/wish/ctl/pkg/client" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/wish/ctl/pkg/client" ) -func init() { - viper.SetDefault("selected", make(map[string]location)) - viper.AddConfigPath("$HOME/.kron") - err := viper.ReadInConfig() - if err != nil { - // Write config file - fmt.Println("Creating new config file") - createConfig() - // panic(err.Error()) - } -} - -func GetSelectCmd(c *client.Client) *cobra.Command { +func selectCmd(c *client.Client) *cobra.Command { return &cobra.Command{ Use: "select job [flags]", Short: "Uses list to select a job to operate on", @@ -27,6 +14,17 @@ func GetSelectCmd(c *client.Client) *cobra.Command { A namespace and contexts can be specified to limit matches. If namespace/contexts are not specified, usage will match with all results.`, Args: cobra.ExactArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + viper.SetDefault("favorites", make(map[string]location)) + viper.SetConfigName("config") + viper.AddConfigPath("$HOME/.kron") + err := viper.ReadInConfig() + if err != nil { + // Write config file + cmd.Println("Creating new config file") + createConfig() + } + }, RunE: func(cmd *cobra.Command, args []string) error { // args/flags job := args[0] diff --git a/cmd/kron/suspend.go b/cmd/kron/suspend.go index 32db5ccf..3c9fc13b 100644 --- a/cmd/kron/suspend.go +++ b/cmd/kron/suspend.go @@ -1,12 +1,12 @@ package kron import ( + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" ) -func GetSuspendCmd(c *client.Client) *cobra.Command { +func suspendCmd(c *client.Client) *cobra.Command { return &cobra.Command{ Use: "suspend cronjob [flags]", Short: "Suspend a cron job", diff --git a/cmd/kron/unfavorite.go b/cmd/kron/unfavorite.go index 6c146136..a6b9e60b 100644 --- a/cmd/kron/unfavorite.go +++ b/cmd/kron/unfavorite.go @@ -1,32 +1,29 @@ package kron import ( - "fmt" - "github.com/wish/ctl/pkg/client" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/wish/ctl/pkg/client" ) -func init() { - viper.SetDefault("favorites", make(map[string]location)) - viper.SetConfigName("config") - viper.AddConfigPath("$HOME/.kron") - err := viper.ReadInConfig() - if err != nil { - // Write config file - fmt.Println("Creating new config file") - createConfig() - // panic(err.Error()) - } -} - -func GetUnfavoriteCmd(c *client.Client) *cobra.Command { +func unfavoriteCmd(c *client.Client) *cobra.Command { return &cobra.Command{ Use: "unfavorite jobs", Short: "Removes job(s) from favorite list", Long: `Removes job(s) from favorite list. If no jobs are specified, removes selected job.`, Args: cobra.MinimumNArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + viper.SetDefault("favorites", make(map[string]location)) + viper.SetConfigName("config") + viper.AddConfigPath("$HOME/.kron") + err := viper.ReadInConfig() + if err != nil { + // Write config file + cmd.Println("Creating new config file") + createConfig() + } + }, RunE: func(cmd *cobra.Command, args []string) error { // Behaviour when var f map[string]location diff --git a/cmd/kron/unsuspend.go b/cmd/kron/unsuspend.go index f039c478..3b7ec801 100644 --- a/cmd/kron/unsuspend.go +++ b/cmd/kron/unsuspend.go @@ -1,12 +1,12 @@ package kron import ( + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" ) -func GetUnsuspendCmd(c *client.Client) *cobra.Command { +func unsuspendCmd(c *client.Client) *cobra.Command { return &cobra.Command{ Use: "unsuspend cronjob [flags]", Short: "Unsuspend a cron job", diff --git a/cmd/kron/web.go b/cmd/kron/web.go index 162f9ee5..480ef5bb 100644 --- a/cmd/kron/web.go +++ b/cmd/kron/web.go @@ -1,12 +1,12 @@ package kron import ( + "github.com/spf13/cobra" "github.com/wish/ctl/pkg/client" "github.com/wish/ctl/pkg/web" - "github.com/spf13/cobra" ) -func GetWebCmd(c *client.Client) *cobra.Command { +func webCmd(c *client.Client) *cobra.Command { return &cobra.Command{ Use: "web [port]", Short: "Serves a web ui of kron features", diff --git a/cmd/logs.go b/cmd/logs.go index 9bbb06fd..7f47bc50 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -2,12 +2,12 @@ package cmd import ( "fmt" + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" ) -func GetLogsCmd(c *client.Client) *cobra.Command { +func logsCmd(c *client.Client) *cobra.Command { ret := &cobra.Command{ Use: "logs pod [flags]", Aliases: []string{"log"}, diff --git a/cmd/root.go b/cmd/root.go index ee1619df..2c401846 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,38 +1,40 @@ package cmd import ( + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/kron" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" "os" ) -func init() { - rootCmd.PersistentFlags().StringSliceP("context", "x", nil, "Specify the context(s) to operate in") - rootCmd.PersistentFlags().StringP("namespace", "n", "", "Specify the namespace within all the contexts specified") - rootCmd.PersistentFlags().StringArrayP("label", "l", nil, "Filter objects by label") +func cmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "ctl", + Short: "A CLI tool for discovering k8s pods/logs across multiple clusters", + Long: `ctl is a CLI tool for easily getting/exec pods/logs across multiple clusters/namespaces. If you have any questions, problems, or requests please ask #automation.`, + SilenceUsage: true, + } - // Commands' + // If config flag is set here... c := client.GetDefaultConfigClient() - rootCmd.AddCommand(GetDescribeCmd(c)) - rootCmd.AddCommand(GetGetCmd(c)) - rootCmd.AddCommand(GetLogsCmd(c)) - rootCmd.AddCommand(GetShCmd(c)) - rootCmd.AddCommand(kron.GetKronCmd(c)) -} + cmd.AddCommand(describeCmd(c)) + cmd.AddCommand(getCmd(c)) + cmd.AddCommand(logsCmd(c)) + cmd.AddCommand(shCmd(c)) + cmd.AddCommand(versionCmd(c)) + cmd.AddCommand(kron.Cmd(c)) + + cmd.PersistentFlags().StringSliceP("context", "x", nil, "Specify the context(s) to operate in") + cmd.PersistentFlags().StringP("namespace", "n", "", "Specify the namespace within all the contexts specified") + cmd.PersistentFlags().StringArrayP("label", "l", nil, "Filter objects by label") -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "ctl", - Short: "A CLI tool for discovering k8s pods/logs across multiple clusters", - Long: `ctl is a CLI tool for easily getting/exec pods/logs across multiple clusters/namespaces. If you have any questions, problems, or requests please ask #automation.`, - SilenceUsage: true, + return cmd } // 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 { + if err := cmd().Execute(); err != nil { // No printing of err needed because it already errors?? os.Exit(1) } diff --git a/cmd/sh.go b/cmd/sh.go index 54fa58e4..cffc71f4 100644 --- a/cmd/sh.go +++ b/cmd/sh.go @@ -1,13 +1,13 @@ package cmd import ( + "github.com/spf13/cobra" "github.com/wish/ctl/cmd/util/parsing" "github.com/wish/ctl/pkg/client" - "github.com/spf13/cobra" "os" ) -func GetShCmd(c *client.Client) *cobra.Command { +func shCmd(c *client.Client) *cobra.Command { cmd := &cobra.Command{ Use: "sh pod [flags]", Short: "Exec $SHELL into the container of a specific pod", diff --git a/cmd/util/parsing/matchlabel.go b/cmd/util/parsing/matchlabel.go index 763e3d20..ac388f79 100644 --- a/cmd/util/parsing/matchlabel.go +++ b/cmd/util/parsing/matchlabel.go @@ -7,6 +7,8 @@ import ( "strings" ) +// LabelMatch takes in a single label specification string and returns +// a corresponding filter.LabelMatch. func LabelMatch(s string) (filter.LabelMatch, error) { if sub := regexp.MustCompile(`\A(\w+)=(\w+)\z`).FindStringSubmatch(s); len(sub) > 1 { return &filter.LabelMatchEq{sub[1], sub[2]}, nil @@ -19,10 +21,11 @@ func LabelMatch(s string) (filter.LabelMatch, error) { } return lm, nil } else { // Did not match any - return nil, errors.New("No label format found") + return nil, errors.New("no label format found") } } +// LabelMatchSlice is like LabelMatch but handles multiple labels func LabelMatchSlice(s []string) (filter.LabelMatch, error) { if len(s) == 0 { return nil, nil diff --git a/cmd/util/parsing/options.go b/cmd/util/parsing/options.go index 587968d0..8b5d90d8 100644 --- a/cmd/util/parsing/options.go +++ b/cmd/util/parsing/options.go @@ -1,26 +1,31 @@ package parsing import ( + "github.com/spf13/cobra" "github.com/wish/ctl/pkg/client" "github.com/wish/ctl/pkg/client/filter" - "github.com/spf13/cobra" ) +// LabelMatchFromCmd automatically parses the "label" flag from a command +// and returns the filtering.LabelMatch specified. func LabelMatchFromCmd(cmd *cobra.Command) (filter.LabelMatch, error) { s, _ := cmd.Flags().GetStringArray("label") return LabelMatchSlice(s) } +// ListOptions parses a client.ListOptions from a command func ListOptions(cmd *cobra.Command) (client.ListOptions, error) { l, err := LabelMatchFromCmd(cmd) return client.ListOptions{l}, err } +// GetOptions parses a client.GetOptions from a command func GetOptions(cmd *cobra.Command) (client.GetOptions, error) { l, err := LabelMatchFromCmd(cmd) return client.GetOptions{l}, err } +// LogOptions parses a client.LogOptions from a command func LogOptions(cmd *cobra.Command) (client.LogOptions, error) { l, err := LabelMatchFromCmd(cmd) return client.LogOptions{l}, err diff --git a/cmd/version.go b/cmd/version.go index 31cb3515..7c7dc8d4 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -2,19 +2,18 @@ package cmd import ( "github.com/spf13/cobra" + "github.com/wish/ctl/pkg/client" ) // Version set default value var Version = "unset" -func init() { - rootCmd.AddCommand(versionCmd) -} - -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Show ctl version", - Run: func(cmd *cobra.Command, args []string) { - cmd.Println(rootCmd.Use + " version: " + Version) - }, +func versionCmd(*client.Client) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Show ctl version", + Run: func(cmd *cobra.Command, args []string) { + cmd.Println(cmd.Use + " version: " + Version) + }, + } } diff --git a/pkg/client/client.go b/pkg/client/client.go index 983954fe..50032096 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -5,7 +5,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" - _ "k8s.io/client-go/plugin/pkg/client/auth" + _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" // for "oidc" auth provider restclient "k8s.io/client-go/rest" ) @@ -28,6 +28,7 @@ func clientsetHelper(getConfig func() (*restclient.Config, error)) (kubernetes.I return clientset, err } +// GetDefaultConfigClient returns a functioning client from the default kubeconfig path func GetDefaultConfigClient() *Client { return &Client{ &configClientsetGetter{ @@ -40,6 +41,7 @@ func GetDefaultConfigClient() *Client { } } +// GetFakeConfigClient returns a fake client with the objects in the clusters specified func GetFakeConfigClient(clusters map[string][]runtime.Object) *Client { clientsets := make(map[string]kubernetes.Interface) var contexts []string diff --git a/pkg/client/contextsinterface.go b/pkg/client/contextsinterface.go index e100cd67..f6783f2c 100644 --- a/pkg/client/contextsinterface.go +++ b/pkg/client/contextsinterface.go @@ -4,10 +4,13 @@ type contextsGetter interface { GetAllContexts() []string } +// StaticContextsGetter returns a static list of contexts. +// This struct implements the contextsGetter interface type StaticContextsGetter struct { contexts []string } +// GetAllContexts returns the list of all the clusters func (s StaticContextsGetter) GetAllContexts() []string { return s.contexts } diff --git a/pkg/client/cronjobutil.go b/pkg/client/cronjobutil.go index 77313ed7..fb5a574c 100644 --- a/pkg/client/cronjobutil.go +++ b/pkg/client/cronjobutil.go @@ -1,7 +1,7 @@ package client -// Sets the cron job suspend state to given value. -// Does nothing (and returns false) if already set. +// SetCronJobSuspend sets the cron job suspend state to given value and reports whether successful. +// Does nothing (and returns false) if the cronjob is already in the correct state. func (c *Client) SetCronJobSuspend(contexts []string, namespace, name string, suspend bool, options ListOptions) (bool, error) { cronjob, err := c.findCronJob(contexts, namespace, name, options) if err != nil { diff --git a/pkg/client/execinpod.go b/pkg/client/execinpod.go index 5b2d046e..cd42f383 100644 --- a/pkg/client/execinpod.go +++ b/pkg/client/execinpod.go @@ -9,7 +9,7 @@ import ( "k8s.io/client-go/tools/remotecommand" ) -// TODO: Add options param??? +// ExecInPod executes a command on a pod interactively func (c *Client) ExecInPod(contexts []string, namespace, name, container string, options ListOptions, commands []string, stdin io.Reader, stdout, stderr io.Writer) error { pod, container, err := c.findPodWithContainer(contexts, namespace, name, container, options) if err != nil { diff --git a/pkg/client/filter/applylabels.go b/pkg/client/filter/applylabels.go index d787bfc5..78f8cf2e 100644 --- a/pkg/client/filter/applylabels.go +++ b/pkg/client/filter/applylabels.go @@ -1,8 +1,9 @@ package filter -func MatchLabel(l Labeled, m LabelMatch) bool { - if m == nil { +// MatchLabel checks if an object satisfies the requirements of a Labelmatch +func MatchLabel(obj Labeled, lm LabelMatch) bool { + if lm == nil { return true } - return m.Match(l.GetLabels()) + return lm.Match(obj.GetLabels()) } diff --git a/pkg/client/filter/labels.go b/pkg/client/filter/labels.go index 8ab40b6c..7605829e 100644 --- a/pkg/client/filter/labels.go +++ b/pkg/client/filter/labels.go @@ -1,18 +1,22 @@ package filter +// Labeled is used to access labels of an object type Labeled interface { GetLabels() map[string]string } +// LabelMatch is used to filter objects with labels type LabelMatch interface { Match(map[string]string) bool } +// LabelMatchEq checks if the value of a label is equal to a value type LabelMatchEq struct { Key string Value string } +// Match checks the label value for equality func (m *LabelMatchEq) Match(labels map[string]string) bool { if v, ok := labels[m.Key]; ok { return v == m.Value @@ -20,11 +24,13 @@ func (m *LabelMatchEq) Match(labels map[string]string) bool { return false } +// LabelMatchNeq checks if the value of a label is non-existent or not equal to a value type LabelMatchNeq struct { Key string Value string } +// Match checks the label value for non-equality func (m *LabelMatchNeq) Match(labels map[string]string) bool { if v, ok := labels[m.Key]; ok { return v != m.Value @@ -32,11 +38,13 @@ func (m *LabelMatchNeq) Match(labels map[string]string) bool { return true } +// LabelMatchSetIn checks if the value of a label is non-existent or is one of multiple values type LabelMatchSetIn struct { Key string Values []string } +// Match checks the label value belongs in a set func (m *LabelMatchSetIn) Match(labels map[string]string) bool { if v, ok := labels[m.Key]; ok { for _, vals := range m.Values { @@ -48,10 +56,12 @@ func (m *LabelMatchSetIn) Match(labels map[string]string) bool { return false } +// LabelMatchMultiple checks that multiple label criteria are satisfied type LabelMatchMultiple struct { Matches []LabelMatch } +// Match checks all nested LabelMatches func (m *LabelMatchMultiple) Match(labels map[string]string) bool { for _, match := range m.Matches { if !match.Match(labels) { diff --git a/pkg/client/findcronjob.go b/pkg/client/findcronjob.go index 06ba699b..22495c6f 100644 --- a/pkg/client/findcronjob.go +++ b/pkg/client/findcronjob.go @@ -21,7 +21,7 @@ func (c *Client) findCronJob(contexts []string, namespace, name string, options } if cron.Name != name { // Pod not found - return nil, errors.New("Cron job not found") // TODO return value + return nil, errors.New("cron job not found") // TODO return value } return &cron, nil diff --git a/pkg/client/findpod.go b/pkg/client/findpod.go index 66519289..7af11b2e 100644 --- a/pkg/client/findpod.go +++ b/pkg/client/findpod.go @@ -22,7 +22,7 @@ func (c *Client) findPod(contexts []string, namespace, name string, options List } if pod.Name != name { // Pod not found - return nil, errors.New("Pod not found") // TODO return value + return nil, errors.New("pod not found") // TODO return value } return &pod, nil diff --git a/pkg/client/findrun.go b/pkg/client/findrun.go index 13c3e6c1..b0ac65cb 100644 --- a/pkg/client/findrun.go +++ b/pkg/client/findrun.go @@ -21,7 +21,7 @@ func (c *Client) findRun(contexts []string, namespace, name string, options List } if run.Name != name { // Pod not found - return nil, errors.New("Cron job run with name \"" + name + "\" not found") // TODO return value + return nil, errors.New("cron job run with name \"" + name + "\" not found") // TODO return value } return &run, nil diff --git a/pkg/client/getcronjobs.go b/pkg/client/getcronjobs.go index eeb5b8ea..c87cdb06 100644 --- a/pkg/client/getcronjobs.go +++ b/pkg/client/getcronjobs.go @@ -7,6 +7,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// GetCronJob returns a single cron job func (c *Client) GetCronJob(context, namespace string, name string, options GetOptions) (*types.CronJobDiscovery, error) { cs, err := c.getContextInterface(context) if err != nil { @@ -20,11 +21,12 @@ func (c *Client) GetCronJob(context, namespace string, name string, options GetO d := types.CronJobDiscovery{context, *cronjob} if !filter.MatchLabel(d, options.LabelMatch) { - return nil, errors.New("Found object does not satisfy filters") + return nil, errors.New("found object does not satisfy filters") } return &d, nil } +// FindCronJobs simultaneously searches for multiple cron jobs and returns all results func (c *Client) FindCronJobs(contexts []string, namespace string, names []string, options ListOptions) ([]types.CronJobDiscovery, error) { if len(contexts) == 0 { contexts = c.GetAllContexts() diff --git a/pkg/client/getpods.go b/pkg/client/getpods.go index 8d49b543..3b7e8579 100644 --- a/pkg/client/getpods.go +++ b/pkg/client/getpods.go @@ -7,6 +7,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// GetPod returns a single pod func (c *Client) GetPod(context, namespace string, name string, options GetOptions) (*types.PodDiscovery, error) { cs, err := c.getContextInterface(context) if err != nil { @@ -19,11 +20,12 @@ func (c *Client) GetPod(context, namespace string, name string, options GetOptio d := types.PodDiscovery{context, *pod} if !filter.MatchLabel(d, options.LabelMatch) { - return nil, errors.New("Found object does not satisfy filters") + return nil, errors.New("found object does not satisfy filters") } return &d, nil } +// FindPods simultaneously searches for multiple pods and returns all results func (c *Client) FindPods(contexts []string, namespace string, names []string, options ListOptions) ([]types.PodDiscovery, error) { if len(contexts) == 0 { contexts = c.GetAllContexts() diff --git a/pkg/client/getruns.go b/pkg/client/getruns.go index 7eb5c693..ea8a5c64 100644 --- a/pkg/client/getruns.go +++ b/pkg/client/getruns.go @@ -7,6 +7,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// GetRun returns a single job func (c *Client) GetRun(context, namespace string, name string, options GetOptions) (*types.RunDiscovery, error) { cs, err := c.getContextInterface(context) if err != nil { @@ -19,11 +20,12 @@ func (c *Client) GetRun(context, namespace string, name string, options GetOptio d := types.RunDiscovery{context, *job} if !filter.MatchLabel(d, options.LabelMatch) { - return nil, errors.New("Found object does not satisfy filters") + return nil, errors.New("found object does not satisfy filters") } return &d, nil } +// FindRuns simultaneously searches for multiple jobs and returns all results func (c *Client) FindRuns(contexts []string, namespace string, names []string, options ListOptions) ([]types.RunDiscovery, error) { if len(contexts) == 0 { contexts = c.GetAllContexts() diff --git a/pkg/client/helper/helper.go b/pkg/client/helper/helper.go index 36fdb860..41d3c513 100644 --- a/pkg/client/helper/helper.go +++ b/pkg/client/helper/helper.go @@ -9,6 +9,7 @@ import ( "k8s.io/client-go/tools/clientcmd" ) +// GetKubeConfigPath returns the default location of a kubeconfig file func GetKubeConfigPath() string { // For multiple calls if fl := flag.Lookup("kubeconfig"); fl != nil { @@ -29,6 +30,7 @@ func GetKubeConfigPath() string { return *kubeconfig } +// GetContexts returns a list of clusters from a config file func GetContexts(configpath string) []string { config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( &clientcmd.ClientConfigLoadingRules{ExplicitPath: configpath}, @@ -39,7 +41,7 @@ func GetContexts(configpath string) []string { } ctxs := make([]string, 0, len(config.Contexts)) - for k, _ := range config.Contexts { // Currently ignoring mappings + for k := range config.Contexts { // Currently ignoring mappings // Hardcode ignore test clusters if !strings.Contains(k, "test") { // REVIEW: Remove this when possible ctxs = append(ctxs, k) diff --git a/pkg/client/interface.go b/pkg/client/interface.go index ae16627c..674fbaa0 100644 --- a/pkg/client/interface.go +++ b/pkg/client/interface.go @@ -48,5 +48,5 @@ func (f *fakeClientsetGetter) getContextInterface(context string) (kubernetes.In if cs, ok := f.clientsets[context]; ok { return cs, nil } - return nil, errors.New("The context specified does not exist") + return nil, errors.New("the context specified does not exist") } diff --git a/pkg/client/listcronjobs.go b/pkg/client/listcronjobs.go index 76a628b7..c79ba273 100644 --- a/pkg/client/listcronjobs.go +++ b/pkg/client/listcronjobs.go @@ -11,6 +11,7 @@ import ( "sync" ) +// ListCronJobs returns a list of all cron jobs that match the query func (c *Client) ListCronJobs(context string, namespace string, options ListOptions) ([]types.CronJobDiscovery, error) { cs, err := c.getContextInterface(context) if err != nil { @@ -30,6 +31,7 @@ func (c *Client) ListCronJobs(context string, namespace string, options ListOpti return items, nil } +// ListCronJobsOverContexts is like ListCronJobs but operates over multiple clusters func (c *Client) ListCronJobsOverContexts(contexts []string, namespace string, options ListOptions) ([]types.CronJobDiscovery, error) { if len(contexts) == 0 { contexts = c.GetAllContexts() @@ -63,7 +65,7 @@ func (c *Client) ListCronJobsOverContexts(contexts []string, namespace string, o wait.Wait() if failed != nil { - return ret, errors.New("Failed connecting to the following contexts: " + strings.Join(failed, ", ")) + return ret, errors.New("failed connecting to the following contexts: " + strings.Join(failed, ", ")) } return ret, nil } diff --git a/pkg/client/listpods.go b/pkg/client/listpods.go index 4de7fdfc..ccc55362 100644 --- a/pkg/client/listpods.go +++ b/pkg/client/listpods.go @@ -11,6 +11,7 @@ import ( "sync" ) +// ListPods returns a list of all pod that match the query func (c *Client) ListPods(context string, namespace string, options ListOptions) ([]types.PodDiscovery, error) { cs, err := c.getContextInterface(context) if err != nil { @@ -30,6 +31,7 @@ func (c *Client) ListPods(context string, namespace string, options ListOptions) return items, nil } +// ListPodsOverContexts is like ListPods but operates over multiple clusters func (c *Client) ListPodsOverContexts(contexts []string, namespace string, options ListOptions) ([]types.PodDiscovery, error) { if len(contexts) == 0 { contexts = c.GetAllContexts() @@ -65,11 +67,12 @@ func (c *Client) ListPodsOverContexts(contexts []string, namespace string, optio wait.Wait() if failed != nil { - return ret, errors.New("Failed connecting to the following contexts: " + strings.Join(failed, ", ")) + return ret, errors.New("failed connecting to the following contexts: " + strings.Join(failed, ", ")) } return ret, nil } +// ListPodsOfRun returns a list of all pods belonging to a job func (c *Client) ListPodsOfRun(contexts []string, namespace, runName string, options ListOptions) ([]types.PodDiscovery, error) { pods, err := c.ListPodsOverContexts(contexts, namespace, options) if err != nil { diff --git a/pkg/client/listruns.go b/pkg/client/listruns.go index 7d02d44c..dc8c1d3f 100644 --- a/pkg/client/listruns.go +++ b/pkg/client/listruns.go @@ -11,6 +11,7 @@ import ( "sync" ) +// ListRuns returns a list of all jobs that match the query func (c *Client) ListRuns(context string, namespace string, options ListOptions) ([]types.RunDiscovery, error) { cs, err := c.getContextInterface(context) if err != nil { @@ -30,6 +31,7 @@ func (c *Client) ListRuns(context string, namespace string, options ListOptions) return items, nil } +// ListRunsOverContexts is like ListRuns but operates over multiple clusters func (c *Client) ListRunsOverContexts(contexts []string, namespace string, options ListOptions) ([]types.RunDiscovery, error) { if len(contexts) == 0 { contexts = c.GetAllContexts() @@ -63,12 +65,12 @@ func (c *Client) ListRunsOverContexts(contexts []string, namespace string, optio wait.Wait() if failed != nil { - return ret, errors.New("Failed connecting to the following contexts: " + strings.Join(failed, ", ")) + return ret, errors.New("failed connecting to the following contexts: " + strings.Join(failed, ", ")) } return ret, nil } -// Also over contexts +// ListRunsOfCronJob returns a list of all jobs belonging to a cron job func (c *Client) ListRunsOfCronJob(contexts []string, namespace, cronjobName string, options ListOptions) ([]types.RunDiscovery, error) { cronjob, err := c.findCronJob(contexts, namespace, cronjobName, options) if err != nil { diff --git a/pkg/client/logpods.go b/pkg/client/logpods.go index 6e3936b7..0fb3d152 100644 --- a/pkg/client/logpods.go +++ b/pkg/client/logpods.go @@ -5,7 +5,7 @@ import ( "k8s.io/client-go/rest" ) -// Retrieves logs of a single pod (uses first found if multiple) +// LogPodOverContexts retrieves logs of a single pod (uses first found if multiple) func (c *Client) LogPodOverContexts(contexts []string, namespace, name, container string, options LogOptions) (*rest.Result, error) { pod, container, err := c.findPodWithContainer(contexts, namespace, name, container, ListOptions{options.LabelMatch}) if err != nil { @@ -22,7 +22,8 @@ func (c *Client) LogPodOverContexts(contexts []string, namespace, name, containe return &res, nil } -// Only logs first container if container not specified +// LogPod retrieves logs from a container of a pod. +// Operates on the first container if none specified. // TODO: The usage of this function is odd (support all containers???) func (c *Client) LogPod(context, namespace, name, container string, options LogOptions) (*rest.Result, error) { cl, err := c.getContextInterface(context) diff --git a/pkg/client/options.go b/pkg/client/options.go index a8b4b7f4..8e001047 100644 --- a/pkg/client/options.go +++ b/pkg/client/options.go @@ -2,14 +2,19 @@ package client import "github.com/wish/ctl/pkg/client/filter" +// Currently, all three options are the same and only support filtering. + +// ListOptions is used to specific filtering on list operations type ListOptions struct { filter.LabelMatch } +// GetOptions is used to specific filtering on get operations type GetOptions struct { filter.LabelMatch } +// LogOptions is used to specific filtering on log operations type LogOptions struct { filter.LabelMatch } diff --git a/pkg/client/runcronjob.go b/pkg/client/runcronjob.go index 2d59ada4..cb39dafd 100644 --- a/pkg/client/runcronjob.go +++ b/pkg/client/runcronjob.go @@ -8,7 +8,7 @@ import ( "time" ) -// Creates a new job with timestamp from the specified cron job template +// RunCronJob creates a new job with timestamp from the specified cron job template func (c *Client) RunCronJob(contexts []string, namespace, cronjobName string, options ListOptions) (*types.RunDiscovery, error) { cronjob, err := c.findCronJob(contexts, namespace, cronjobName, options) if err != nil { diff --git a/pkg/client/types/types.go b/pkg/client/types/types.go index 616e20ef..5b10ece8 100644 --- a/pkg/client/types/types.go +++ b/pkg/client/types/types.go @@ -6,30 +6,35 @@ import ( corev1 "k8s.io/api/core/v1" ) -// Found resources with the context as client-go does not contain context +// CronJobDiscovery represents a cron job with the context information type CronJobDiscovery struct { Context string batchv1beta1.CronJob } +// GetLabels allows CronJobDiscovery to implement the Labeled interface func (c CronJobDiscovery) GetLabels() map[string]string { return c.Labels } +// RunDiscovery represents a job with the context information type RunDiscovery struct { Context string batchv1.Job } +// GetLabels allows RunDiscovery to implement the Labeled interface func (c RunDiscovery) GetLabels() map[string]string { return c.Labels } +// PodDiscovery represents a pod with the context information type PodDiscovery struct { Context string corev1.Pod } +// GetLabels allows PodDiscovery to implement the Labeled interface func (c PodDiscovery) GetLabels() map[string]string { return c.Labels } diff --git a/pkg/web/main.go b/pkg/web/main.go index e294917e..aefa4d78 100644 --- a/pkg/web/main.go +++ b/pkg/web/main.go @@ -11,6 +11,7 @@ import ( "strings" ) +// Serve runs a webserver for kron at the specified url func Serve(endpoint string) { cl := client.GetDefaultConfigClient() diff --git a/pkg/web/process.go b/pkg/web/process.go index 200b52ab..1ef8e795 100644 --- a/pkg/web/process.go +++ b/pkg/web/process.go @@ -23,28 +23,29 @@ type cardDetails struct { Namespace string Active int Suspend bool - LastRun RunStatus + LastRun runStatus } -type RunStatus string +// +type runStatus string const ( - RunFailed RunStatus = "failed" - RunSuccess RunStatus = "success" - RunNA RunStatus = "N/A" - RunRunning RunStatus = "running" + runFailed runStatus = "failed" + runSuccess runStatus = "success" + runNA runStatus = "N/A" + runRunning runStatus = "running" ) func toCardDetails(c *dtypes.CronJobDiscovery, r *dtypes.RunDiscovery) cardDetails { - var runStatus RunStatus + var runStatus runStatus if r == nil { - runStatus = RunNA + runStatus = runNA } else if r.Status.Failed > 0 { - runStatus = RunFailed + runStatus = runFailed } else if r.Status.CompletionTime != nil { - runStatus = RunSuccess + runStatus = runSuccess } else { - runStatus = RunRunning + runStatus = runRunning } return cardDetails{ @@ -61,7 +62,7 @@ func toCardDetailsList(lst []dtypes.CronJobDiscovery, runs []dtypes.RunDiscovery ret := make([]cardDetails, len(lst)) recent := make(map[types.UID]*dtypes.RunDiscovery) - for i, _ := range runs { + for i := range runs { if len(runs[i].OwnerReferences) == 1 { if x, ok := recent[runs[i].OwnerReferences[0].UID]; !ok || runs[i].Status.StartTime.After(x.Status.StartTime.Time) { recent[runs[i].OwnerReferences[0].UID] = &runs[i] @@ -69,7 +70,7 @@ func toCardDetailsList(lst []dtypes.CronJobDiscovery, runs []dtypes.RunDiscovery } } - for i, _ := range lst { + for i := range lst { ret[i] = toCardDetails(&lst[i], recent[lst[i].UID]) } return ret