From 56e4dc9ba8ada12f87453868bbce27a1de3d9a0f Mon Sep 17 00:00:00 2001 From: derailed Date: Fri, 15 Nov 2019 10:06:30 -0700 Subject: [PATCH] checkpoint --- .golangci.yml | 15 ++- cmd/root.go | 32 +++-- internal/color/colorize_test.go | 3 +- internal/config/alias.go | 45 ++++--- internal/config/alias_test.go | 16 +-- internal/config/bench_test.go | 12 +- internal/config/config.go | 8 +- internal/config/config_test.go | 9 +- internal/k8s/api.go | 13 +- internal/k8s/cluster.go | 8 +- internal/k8s/config.go | 3 - internal/k8s/context.go | 4 +- internal/k8s/cronjob.go | 7 +- internal/k8s/gvr_test.go | 30 +++-- internal/k8s/hpa_v2beta2.go | 2 - internal/k8s/metrics.go | 16 ++- internal/k8s/metrics_test.go | 6 +- internal/k8s/port_forward.go | 70 ++++++----- internal/model/hint_test.go | 3 +- internal/model/menu_hint.go | 2 +- internal/model/stack_test.go | 6 +- internal/perf/benchmark.go | 15 +-- internal/resource/base.go | 12 +- internal/resource/container.go | 13 +- internal/resource/container_test.go | 12 +- internal/resource/context_test.go | 8 -- internal/resource/cr.go | 7 +- internal/resource/cr_binding.go | 6 +- internal/resource/cr_binding_test.go | 2 +- internal/resource/cr_test.go | 2 +- internal/resource/crd.go | 44 +++++-- internal/resource/crd_test.go | 27 ----- internal/resource/cronjob.go | 6 +- internal/resource/custom.go | 37 ++++-- internal/resource/dp.go | 11 +- internal/resource/ds.go | 11 +- internal/resource/ep.go | 6 +- internal/resource/ep_test.go | 5 - internal/resource/evt.go | 35 +----- internal/resource/helpers.go | 49 ++------ internal/resource/helpers_test.go | 5 +- internal/resource/hpa_v1.go | 10 +- internal/resource/hpa_v2beta1.go | 12 +- internal/resource/hpa_v2beta2.go | 36 ++++-- internal/resource/ing.go | 6 +- internal/resource/job.go | 13 +- internal/resource/job_int_test.go | 6 +- internal/resource/list.go | 22 ++-- internal/resource/log_options.go | 2 +- internal/resource/no.go | 151 +++++++++++++----------- internal/resource/no_int_test.go | 2 +- internal/resource/np.go | 6 +- internal/resource/ns.go | 7 +- internal/resource/pdb.go | 11 +- internal/resource/pod.go | 58 +++++---- internal/resource/pod_int_test.go | 14 +-- internal/resource/pod_test.go | 11 +- internal/resource/pv.go | 8 +- internal/resource/pvc.go | 9 +- internal/resource/rc.go | 6 +- internal/resource/ro.go | 6 +- internal/resource/ro_binding.go | 7 +- internal/resource/ro_binding_test.go | 5 - internal/resource/ro_test.go | 5 - internal/resource/rs.go | 6 +- internal/resource/rs_test.go | 5 - internal/resource/sa.go | 6 +- internal/resource/sa_test.go | 2 +- internal/resource/sc.go | 7 +- internal/resource/sts.go | 11 +- internal/resource/sts_test.go | 2 +- internal/resource/svc.go | 12 +- internal/resource/svc_int_test.go | 6 +- internal/resource/svc_test.go | 2 +- internal/ui/action_test.go | 2 +- internal/ui/app_test.go | 4 +- internal/ui/cmd.go | 4 - internal/ui/cmd_buff.go | 8 +- internal/ui/colorer_test.go | 3 +- internal/ui/config.go | 4 +- internal/ui/deltas.go | 119 ++++++++++++------- internal/ui/dialog/port_forward_test.go | 3 +- internal/ui/logo_test.go | 3 +- internal/ui/menu.go | 30 ++--- internal/ui/menu_test.go | 3 +- internal/ui/sorter_test.go | 3 +- internal/ui/table.go | 18 ++- internal/ui/table_helper_test.go | 6 +- internal/view/alias.go | 10 +- internal/view/alias_test.go | 2 +- internal/view/app.go | 16 ++- internal/view/bench.go | 49 +++----- internal/view/bench_int_test.go | 3 +- internal/view/cluster_info.go | 4 +- internal/view/colorer.go | 40 ++++--- internal/view/colorer_test.go | 2 +- internal/view/command.go | 15 +-- internal/view/container.go | 8 +- internal/view/context.go | 5 +- internal/view/context_int_test.go | 3 +- internal/view/details.go | 37 +----- internal/view/dp.go | 12 +- internal/view/ds.go | 12 +- internal/view/env_test.go | 3 +- internal/view/help.go | 20 ++-- internal/view/help_test.go | 8 -- internal/view/helpers.go | 2 +- internal/view/helpers_test.go | 14 ++- internal/view/job.go | 8 +- internal/view/log.go | 13 +- internal/view/log_resource.go | 14 +-- internal/view/log_test.go | 2 +- internal/view/logs.go | 25 ++-- internal/view/master_detail.go | 15 ++- internal/view/no.go | 9 +- internal/view/ns.go | 2 +- internal/view/page_stack.go | 2 +- internal/view/pod.go | 19 ++- internal/view/pod_int_test.go | 3 +- internal/view/policy.go | 13 +- internal/view/port_forward.go | 47 ++------ internal/view/port_selector.go | 34 ------ internal/view/rbac.go | 13 +- internal/view/registrar.go | 5 +- internal/view/resource.go | 13 +- internal/view/rs.go | 13 +- internal/view/scalable_resource.go | 6 +- internal/view/screen_dump.go | 35 ++---- internal/view/select_list.go | 15 --- internal/view/sts.go | 11 +- internal/view/subject.go | 19 ++- internal/view/svc.go | 16 +-- internal/view/table.go | 2 +- internal/view/table_helper.go | 13 +- internal/view/yaml.go | 12 +- internal/watch/container.go | 17 +-- internal/watch/helper_test.go | 19 +-- internal/watch/informer.go | 17 +-- internal/watch/no.go | 7 +- internal/watch/no_mx.go | 31 +++-- internal/watch/no_mx_test.go | 6 +- internal/watch/pod.go | 12 +- internal/watch/pod_mx.go | 41 ++++--- internal/watch/pod_mx_test.go | 11 +- 144 files changed, 1093 insertions(+), 1017 deletions(-) delete mode 100644 internal/view/port_selector.go diff --git a/.golangci.yml b/.golangci.yml index f9690f57..e5a47e1c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -117,7 +117,7 @@ linters-settings: min-complexity: 10 gocognit: # minimal code complexity to report, 30 by default (but we recommend 10-20) - min-complexity: 10 + min-complexity: 20 maligned: # print struct with more effective memory layout or not, false by default suggest-new: true @@ -179,8 +179,8 @@ linters-settings: # See https://go-critic.github.io/overview#checks-overview # To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run` # By default list of stable checks is used. - enabled-checks: - - rangeValCopy + # enabled-checks: + # - rangeValCopy # Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty disabled-checks: @@ -230,9 +230,12 @@ linters: enable: - megacheck - govet + - funlen + - gocyclo disable: - maligned - prealloc + - gosec disable-all: false presets: - bugs @@ -256,6 +259,9 @@ issues: - errcheck - dupl - gosec + - funlen + - goconst + - gocognit # Exclude known linters from partially hard-vendored code, # which is impossible to exclude via "nolint" comments. @@ -296,6 +302,5 @@ issues: # Show only new issues created after git revision `REV` new-from-rev: REV - # Show only new issues created in git patch with set file path. - new-from-patch: path/to/patch/file + # new-from-patch: path/to/patch/file diff --git a/cmd/root.go b/cmd/root.go index 56be6a6e..fc4fbded 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -38,16 +38,25 @@ var ( ) func init() { + const falseFlag = "false" rootCmd.AddCommand(versionCmd(), infoCmd()) initK9sFlags() initK8sFlags() // Klogs (of course) want to print stuff to the screen ;( klog.InitFlags(nil) - flag.Set("log_file", config.K9sLogs) - flag.Set("stderrthreshold", "fatal") - flag.Set("alsologtostderr", "false") - flag.Set("logtostderr", "false") + if err := flag.Set("log_file", config.K9sLogs); err != nil { + log.Error().Err(err) + } + if err := flag.Set("stderrthreshold", "fatal"); err != nil { + log.Error().Err(err) + } + if err := flag.Set("alsologtostderr", falseFlag); err != nil { + log.Error().Err(err) + } + if err := flag.Set("logtostderr", falseFlag); err != nil { + log.Error().Err(err) + } } // Execute root command @@ -64,7 +73,7 @@ func run(cmd *cobra.Command, args []string) { log.Error().Msgf("Boom! %v", err) log.Error().Msg(string(debug.Stack())) printLogo(color.Red) - fmt.Printf(color.Colorize("Boom!! ", color.Red)) + fmt.Printf("%s", color.Colorize("Boom!! ", color.Red)) fmt.Println(color.Colorize(fmt.Sprintf("%v.", err), color.White)) } }() @@ -85,6 +94,7 @@ func loadConfiguration() *config.Config { // Load K9s config file... k8sCfg := k8s.NewConfig(k8sFlags) k9sCfg := config.NewConfig(k8sCfg) + if err := k9sCfg.Load(config.K9sConfigFile); err != nil { log.Warn().Msg("Unable to locate K9s config. Generating new configuration...") } @@ -101,8 +111,8 @@ func loadConfiguration() *config.Config { k9sCfg.K9s.OverrideCommand(*k9sFlags.Command) } - if k9sFlags.AllNamespaces != nil && *k9sFlags.AllNamespaces { - k9sCfg.SetActiveNamespace(resource.AllNamespaces) + if isBoolSet(k9sFlags.AllNamespaces) && k9sCfg.SetActiveNamespace(resource.AllNamespaces) != nil { + log.Error().Msg("Setting active namespace") } if err := k9sCfg.Refine(k8sFlags); err != nil { @@ -115,11 +125,17 @@ func loadConfiguration() *config.Config { log.Panic().Err(err).Msg("K9s can't connect to cluster") } log.Info().Msg("✅ Kubernetes connectivity") - k9sCfg.Save() + if err := k9sCfg.Save(); err != nil { + log.Error().Err(err).Msg("Config save") + } return k9sCfg } +func isBoolSet(b *bool) bool { + return b != nil && *b +} + func parseLevel(level string) zerolog.Level { switch level { case "debug": diff --git a/internal/color/colorize_test.go b/internal/color/colorize_test.go index 126f54d1..41f829f3 100644 --- a/internal/color/colorize_test.go +++ b/internal/color/colorize_test.go @@ -17,7 +17,8 @@ func TestColorize(t *testing.T) { "default": {"blee", 0, "\x1b[37mblee\x1b[0m"}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, Colorize(u.s, u.c)) }) diff --git a/internal/config/alias.go b/internal/config/alias.go index aa7df636..281174c8 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -27,6 +27,15 @@ func NewAliases() Aliases { } func (a Aliases) loadDefaults() { + const ( + contexts = "contexts" + portFwds = "portforwards" + benchmarks = "benchmarks" + dumps = "screendumps" + groups = "groups" + users = "users" + ) + a.Alias["dp"] = "apps/v1/deployments" a.Alias["sec"] = "v1/secrets" a.Alias["jo"] = "batch/v1/jobs" @@ -36,34 +45,34 @@ func (a Aliases) loadDefaults() { a.Alias["rob"] = "rbac.authorization.k8s.io/v1/rolebindings" a.Alias["np"] = "networking.k8s.io/v1/networkpolicies" { - a.Alias["ctx"] = "contexts" - a.Alias["contexts"] = "contexts" - a.Alias["context"] = "contexts" + a.Alias["ctx"] = contexts + a.Alias[contexts] = contexts + a.Alias["context"] = contexts } { - a.Alias["usr"] = "users" - a.Alias["users"] = "users" - a.Alias["user"] = "user" + a.Alias["usr"] = users + a.Alias[users] = users + a.Alias["user"] = users } { - a.Alias["grp"] = "groups" - a.Alias["group"] = "groups" - a.Alias["groups"] = "groups" + a.Alias["grp"] = groups + a.Alias["group"] = groups + a.Alias[groups] = groups } { - a.Alias["pf"] = "portforwards" - a.Alias["portforwards"] = "portforwards" - a.Alias["portforward"] = "portforwards" + a.Alias["pf"] = portFwds + a.Alias[portFwds] = portFwds + a.Alias["portforward"] = portFwds } { - a.Alias["be"] = "benchmarks" - a.Alias["benchmark"] = "benchmarks" - a.Alias["benchmarks"] = "benchmarks" + a.Alias["be"] = benchmarks + a.Alias["benchmark"] = benchmarks + a.Alias[benchmarks] = benchmarks } { - a.Alias["sd"] = "screendumps" - a.Alias["screendump"] = "screendumps" - a.Alias["screendumps"] = "screendumps" + a.Alias["sd"] = dumps + a.Alias["screendump"] = dumps + a.Alias[dumps] = dumps } } diff --git a/internal/config/alias_test.go b/internal/config/alias_test.go index 94a14e4b..98c1ac35 100644 --- a/internal/config/alias_test.go +++ b/internal/config/alias_test.go @@ -13,7 +13,7 @@ func TestAliasDefine(t *testing.T) { aliases []string } - tts := []struct { + uu := []struct { name string aliases []aliasDef registeredCommands map[string]string @@ -51,15 +51,16 @@ func TestAliasDefine(t *testing.T) { }, } - for _, tt := range tts { - t.Run(tt.name, func(t *testing.T) { + for i := range uu { + u := uu[i] + t.Run(u.name, func(t *testing.T) { configAlias := config.NewAliases() - for _, aliases := range tt.aliases { + for _, aliases := range u.aliases { for _, a := range aliases.aliases { configAlias.Define(aliases.cmd, a) } } - for alias, cmd := range tt.registeredCommands { + for alias, cmd := range u.registeredCommands { v, ok := configAlias.Get(alias) assert.True(t, ok) assert.Equal(t, cmd, v, "Wrong command for alias "+alias) @@ -70,18 +71,17 @@ func TestAliasDefine(t *testing.T) { func TestAliasesLoad(t *testing.T) { a := config.NewAliases() - assert.Nil(t, a.LoadAliases("test_assets/alias.yml")) + assert.Nil(t, a.LoadAliases("test_assets/alias.yml")) assert.Equal(t, 27, len(a.Alias)) } func TestAliasesSave(t *testing.T) { a := config.NewAliases() - a.Alias["test"] = "fred" a.Alias["blee"] = "duh" - a.SaveAliases("/tmp/a.yml") + assert.Nil(t, a.SaveAliases("/tmp/a.yml")) assert.Nil(t, a.LoadAliases("/tmp/a.yml")) assert.Equal(t, 28, len(a.Alias)) } diff --git a/internal/config/bench_test.go b/internal/config/bench_test.go index bd15f659..8f9aad1e 100644 --- a/internal/config/bench_test.go +++ b/internal/config/bench_test.go @@ -16,7 +16,8 @@ func TestBenchEmpty(t *testing.T) { "notEmpty": {newBenchmark(), false}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, u.b.empty()) }) @@ -46,7 +47,8 @@ func TestBenchLoad(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { b, err := NewBench(u.file) @@ -95,7 +97,8 @@ func TestBenchServiceLoad(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { b, err := NewBench("test_assets/b_good.yml") @@ -165,7 +168,8 @@ func TestBenchContainerLoad(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { b, err := NewBench("test_assets/b_containers.yml") diff --git a/internal/config/config.go b/internal/config/config.go index 8a6a9c22..1818d86d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -84,7 +84,9 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags) error { } c.K9s.CurrentCluster = ctx.Cluster if len(ctx.Namespace) != 0 { - c.SetActiveNamespace(ctx.Namespace) + if err := c.SetActiveNamespace(ctx.Namespace); err != nil { + return err + } } if isSet(flags.ClusterName) { @@ -92,7 +94,9 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags) error { } if isSet(flags.Namespace) { - c.SetActiveNamespace(*flags.Namespace) + if err := c.SetActiveNamespace(*flags.Namespace); err != nil { + return err + } } return nil diff --git a/internal/config/config_test.go b/internal/config/config_test.go index e5763750..7a2d078c 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -54,7 +54,8 @@ func TestConfigRefine(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { mc := NewMockConnection() m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) @@ -142,7 +143,7 @@ func TestConfigSetActiveNamespace(t *testing.T) { cfg := config.NewConfig(mk) assert.Nil(t, cfg.Load("test_assets/k9s.yml")) - cfg.SetActiveNamespace("default") + assert.Nil(t, cfg.SetActiveNamespace("default")) assert.Equal(t, "default", cfg.ActiveNamespace()) } @@ -202,7 +203,7 @@ func TestConfigSaveFile(t *testing.T) { cfg := config.NewConfig(mk) cfg.SetConnection(mc) - cfg.Load("test_assets/k9s.yml") + assert.Nil(t, cfg.Load("test_assets/k9s.yml")) cfg.K9s.RefreshRate = 100 cfg.K9s.LogBufferSize = 500 cfg.K9s.LogRequestSize = 100 @@ -231,7 +232,7 @@ func TestConfigReset(t *testing.T) { cfg := config.NewConfig(mk) cfg.SetConnection(mc) - cfg.Load("test_assets/k9s.yml") + assert.Nil(t, cfg.Load("test_assets/k9s.yml")) cfg.Reset() cfg.Validate() diff --git a/internal/k8s/api.go b/internal/k8s/api.go index c71382a1..607f5909 100644 --- a/internal/k8s/api.go +++ b/internal/k8s/api.go @@ -66,17 +66,12 @@ type ( CanIAccess(ns, rvg string, verbs []string) (bool, error) } - k8sClient struct { - client kubernetes.Interface - dClient dynamic.Interface - nsClient dynamic.NamespaceableResourceInterface - mxsClient *versioned.Clientset - } - // APIClient represents a Kubernetes api client. APIClient struct { - k8sClient - + client kubernetes.Interface + dClient dynamic.Interface + nsClient dynamic.NamespaceableResourceInterface + mxsClient *versioned.Clientset cachedDiscovery *disk.CachedDiscoveryClient config *Config useMetricServer bool diff --git a/internal/k8s/cluster.go b/internal/k8s/cluster.go index 632b13c6..4e82414e 100644 --- a/internal/k8s/cluster.go +++ b/internal/k8s/cluster.go @@ -5,6 +5,8 @@ import ( v1 "k8s.io/api/core/v1" ) +const na = "n/a" + // Cluster represents a Kubernetes cluster. type Cluster struct { Connection @@ -32,7 +34,7 @@ func (c *Cluster) ContextName() string { ctx, err := c.Config().CurrentContextName() if err != nil { c.logger.Warn().Msgf("%s", err) - return "N/A" + return na } return ctx } @@ -42,7 +44,7 @@ func (c *Cluster) ClusterName() string { ctx, err := c.Config().CurrentClusterName() if err != nil { c.logger.Warn().Msgf("%s", err) - return "N/A" + return na } return ctx } @@ -52,7 +54,7 @@ func (c *Cluster) UserName() string { usr, err := c.Config().CurrentUserName() if err != nil { c.logger.Warn().Msgf("%s", err) - return "N/A" + return na } return usr } diff --git a/internal/k8s/config.go b/internal/k8s/config.go index ac4a2bc4..28547b22 100644 --- a/internal/k8s/config.go +++ b/internal/k8s/config.go @@ -12,8 +12,6 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) -const defaultNamespace = "default" - // Config tracks a kubernetes configuration. type Config struct { flags *genericclioptions.ConfigFlags @@ -283,7 +281,6 @@ func (c *Config) ensureConfig() { log.Debug().Msg("Loading raw config from flags...") c.clientConfig = c.flags.ToRawKubeConfigLoader() - return } // ---------------------------------------------------------------------------- diff --git a/internal/k8s/context.go b/internal/k8s/context.go index 0572b93b..acb383c7 100644 --- a/internal/k8s/context.go +++ b/internal/k8s/context.go @@ -99,7 +99,9 @@ func (c *Context) KubeUpdate(n string) error { if err != nil { return err } - c.Switch(n) + if err := c.Switch(n); err != nil { + return err + } return clientcmd.ModifyConfig( clientcmd.NewDefaultPathOptions(), config, true, ) diff --git a/internal/k8s/cronjob.go b/internal/k8s/cronjob.go index 24a9957d..3bc778c1 100644 --- a/internal/k8s/cronjob.go +++ b/internal/k8s/cronjob.go @@ -1,6 +1,8 @@ package k8s import ( + "errors" + batchv1 "k8s.io/api/batch/v1" batchv1beta1 "k8s.io/api/batch/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -50,8 +52,11 @@ func (c *CronJob) Run(ns, n string) error { if err != nil { return err } - cronJob := cj.(*batchv1beta1.CronJob) + cronJob, ok := cj.(*batchv1beta1.CronJob) + if !ok { + return errors.New("Expecting valid cronjob") + } var jobName = cronJob.Name if len(cronJob.Name) >= maxJobNameSize { jobName = cronJob.Name[0:maxJobNameSize] diff --git a/internal/k8s/gvr_test.go b/internal/k8s/gvr_test.go index 3b0b93d7..318733ad 100644 --- a/internal/k8s/gvr_test.go +++ b/internal/k8s/gvr_test.go @@ -13,12 +13,13 @@ func TestAsGR(t *testing.T) { gvr string e schema.GroupVersion }{ - "full": {"apps/v1/deployments", schema.GroupVersion{"apps", "v1"}}, - "core": {"v1/pods", schema.GroupVersion{"", "v1"}}, - "bork": {"users", schema.GroupVersion{"", ""}}, + "full": {"apps/v1/deployments", schema.GroupVersion{Group: "apps", Version: "v1"}}, + "core": {"v1/pods", schema.GroupVersion{Group: "", Version: "v1"}}, + "bork": {"users", schema.GroupVersion{Group: "", Version: ""}}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, k8s.GVR(u.gvr).AsGR()) }) @@ -34,7 +35,8 @@ func TestNewGVR(t *testing.T) { "core": {"", "v1", "pods", "v1/pods"}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, k8s.NewGVR(u.g, u.v, u.r).String()) }) @@ -49,7 +51,8 @@ func TestToGVR(t *testing.T) { "core": {"v1", "pods", "v1/pods"}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, k8s.ToGVR(u.gv, u.r).String()) }) @@ -67,7 +70,8 @@ func TestResName(t *testing.T) { "empty": {"", ".."}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, k8s.GVR(u.gvr).ResName()) }) @@ -85,7 +89,8 @@ func TestToR(t *testing.T) { "empty": {"", ""}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, k8s.GVR(u.gvr).ToR()) }) @@ -103,7 +108,8 @@ func TestToG(t *testing.T) { "empty": {"", ""}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, k8s.GVR(u.gvr).ToG()) }) @@ -121,7 +127,8 @@ func TestToV(t *testing.T) { "empty": {"", ""}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, k8s.GVR(u.gvr).ToV()) }) @@ -138,7 +145,8 @@ func TestToStringer(t *testing.T) { "empty": {""}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.gvr, k8s.GVR(u.gvr).String()) }) diff --git a/internal/k8s/hpa_v2beta2.go b/internal/k8s/hpa_v2beta2.go index aed5892f..cbacfefc 100644 --- a/internal/k8s/hpa_v2beta2.go +++ b/internal/k8s/hpa_v2beta2.go @@ -4,8 +4,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -var supportedAutoScalingAPIVersions = []string{"v2beta2", "v2beta1", "v1"} - // HorizontalPodAutoscalerV2Beta2 represents am HorizontalPodAutoscaler. type HorizontalPodAutoscalerV2Beta2 struct { *base diff --git a/internal/k8s/metrics.go b/internal/k8s/metrics.go index 95a76d30..0671c2de 100644 --- a/internal/k8s/metrics.go +++ b/internal/k8s/metrics.go @@ -1,6 +1,7 @@ package k8s import ( + "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" @@ -51,7 +52,10 @@ func NewMetricsServer(c Connection) *MetricsServer { // NodesMetrics retrieves metrics for a given set of nodes. func (m *MetricsServer) NodesMetrics(nodes Collection, metrics *mv1beta1.NodeMetricsList, mmx NodesMetrics) { for _, n := range nodes { - no := n.(*v1.Node) + no, ok := n.(*v1.Node) + if !ok { + log.Fatal().Msg("Expecting a valid node") + } mmx[no.Name] = NodeMetrics{ AvailCPU: no.Status.Allocatable.Cpu().MilliValue(), AvailMEM: ToMB(no.Status.Allocatable.Memory().Value()), @@ -73,7 +77,10 @@ func (m *MetricsServer) NodesMetrics(nodes Collection, metrics *mv1beta1.NodeMet func (m *MetricsServer) ClusterLoad(nos Collection, nmx Collection, mx *ClusterMetrics) { nodeMetrics := make(NodesMetrics, len(nos)) for _, n := range nos { - no := n.(*v1.Node) + no, ok := n.(*v1.Node) + if !ok { + log.Fatal().Msg("Expecting valid node") + } nodeMetrics[no.Name] = NodeMetrics{ AvailCPU: no.Status.Allocatable.Cpu().MilliValue(), AvailMEM: ToMB(no.Status.Allocatable.Memory().Value()), @@ -81,7 +88,10 @@ func (m *MetricsServer) ClusterLoad(nos Collection, nmx Collection, mx *ClusterM } for _, mx := range nmx { - mxx := mx.(*mv1beta1.NodeMetrics) + mxx, ok := mx.(*mv1beta1.NodeMetrics) + if !ok { + log.Fatal().Msg("Expecting a valid node metric") + } if m, ok := nodeMetrics[mxx.Name]; ok { m.CurrentCPU = mxx.Usage.Cpu().MilliValue() m.CurrentMEM = ToMB(mxx.Usage.Memory().Value()) diff --git a/internal/k8s/metrics_test.go b/internal/k8s/metrics_test.go index 05dd8043..47458bf7 100644 --- a/internal/k8s/metrics_test.go +++ b/internal/k8s/metrics_test.go @@ -54,7 +54,7 @@ func TestNodesMetrics(t *testing.T) { nodes := Collection{ makeNode("n1", "32", "128Gi", "50m", "2Mi"), - makeNode("n2", "8", "4Gi", "50m", "2Mi"), + makeNode("n2", "8", "4Gi", "50m", "10Mi"), } metrics := v1beta1.NodeMetricsList{ @@ -79,8 +79,8 @@ func TestNodesMetrics(t *testing.T) { func BenchmarkNodesMetrics(b *testing.B) { nodes := Collection{ - makeNode("n1", "100m", "4Mi", "50m", "2Mi"), - makeNode("n2", "100m", "4Mi", "50m", "2Mi"), + makeNode("n1", "100m", "4Mi", "100m", "2Mi"), + makeNode("n2", "100m", "4Mi", "100m", "2Mi"), } metrics := v1beta1.NodeMetricsList{ diff --git a/internal/k8s/port_forward.go b/internal/k8s/port_forward.go index 9fdc369d..8aeffa57 100644 --- a/internal/k8s/port_forward.go +++ b/internal/k8s/port_forward.go @@ -4,8 +4,6 @@ import ( "fmt" "net/http" "net/url" - "strconv" - "strings" "time" "github.com/rs/zerolog" @@ -19,7 +17,6 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/tools/portforward" "k8s.io/client-go/transport/spdy" - "k8s.io/kubectl/pkg/util" ) const localhost = "localhost" @@ -150,39 +147,40 @@ func codec() (serializer.CodecFactory, runtime.ParameterCodec) { return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme) } -func svcPortToTargetPort(ports []string, svc v1.Service, pod v1.Pod) ([]string, error) { - var translated []string - for _, port := range ports { - localPort, remotePort := splitPort(port) - portnum, err := strconv.Atoi(remotePort) - if err != nil { - svcPort, err := util.LookupServicePortNumberByName(svc, remotePort) - if err != nil { - return nil, err - } - portnum = int(svcPort) - if localPort == remotePort { - localPort = strconv.Itoa(portnum) - } - } - containerPort, err := util.LookupContainerPortNumberByServicePort(svc, pod, int32(portnum)) - if err != nil { - return nil, err - } - if int32(portnum) != containerPort { - port = fmt.Sprintf("%s:%d", localPort, containerPort) - } - translated = append(translated, port) - } +// BOZO!! +// func svcPortToTargetPort(ports []string, svc v1.Service, pod v1.Pod) ([]string, error) { +// var translated []string +// for _, port := range ports { +// localPort, remotePort := splitPort(port) +// portnum, err := strconv.Atoi(remotePort) +// if err != nil { +// svcPort, e := util.LookupServicePortNumberByName(svc, remotePort) +// if e != nil { +// return nil, e +// } +// portnum = int(svcPort) +// if localPort == remotePort { +// localPort = strconv.Itoa(portnum) +// } +// } +// containerPort, err := util.LookupContainerPortNumberByServicePort(svc, pod, int32(portnum)) +// if err != nil { +// return nil, err +// } +// if int32(portnum) != containerPort { +// port = fmt.Sprintf("%s:%d", localPort, containerPort) +// } +// translated = append(translated, port) +// } - return translated, nil -} +// return translated, nil +// } -func splitPort(port string) (local, remote string) { - parts := strings.Split(port, ":") - if len(parts) == 2 { - return parts[0], parts[1] - } +// func splitPort(port string) (local, remote string) { +// parts := strings.Split(port, ":") +// if len(parts) == 2 { +// return parts[0], parts[1] +// } - return parts[0], parts[0] -} +// return parts[0], parts[0] +// } diff --git a/internal/model/hint_test.go b/internal/model/hint_test.go index b26cfe74..c46f6a41 100644 --- a/internal/model/hint_test.go +++ b/internal/model/hint_test.go @@ -25,7 +25,8 @@ func TestHints(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { h := model.NewHint() l := hintL{count: -1} diff --git a/internal/model/menu_hint.go b/internal/model/menu_hint.go index 47b076cf..4e1f0123 100644 --- a/internal/model/menu_hint.go +++ b/internal/model/menu_hint.go @@ -14,7 +14,7 @@ type MenuHint struct { // IsBlank checks if menu hint is a place holder. func (m MenuHint) IsBlank() bool { - return m.Mnemonic == "" && m.Description == "" && m.Visible == false + return m.Mnemonic == "" && m.Description == "" && !m.Visible } // MenuHints represents a collection of hints. diff --git a/internal/model/stack_test.go b/internal/model/stack_test.go index 2b7f61fe..21c209fa 100644 --- a/internal/model/stack_test.go +++ b/internal/model/stack_test.go @@ -41,7 +41,8 @@ func TestStackPush(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { s := model.NewStack() for _, c := range u.items { @@ -77,7 +78,8 @@ func TestStackTop(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { s := model.NewStack() for _, item := range u.items { diff --git a/internal/perf/benchmark.go b/internal/perf/benchmark.go index 05c84bd4..a082febb 100644 --- a/internal/perf/benchmark.go +++ b/internal/perf/benchmark.go @@ -75,10 +75,6 @@ func (b *Benchmark) init(base string) error { return nil } -func (b *Benchmark) annulled() bool { - return b.canceled -} - // Cancel kills the benchmark in progress. func (b *Benchmark) Cancel() { if b == nil { @@ -118,14 +114,19 @@ func (b *Benchmark) save(cluster string, r io.Reader) error { if err != nil { return err } - defer f.Close() + defer func() { + if e := f.Close(); e != nil { + log.Fatal().Err(e).Msg("Bench save") + } + }() bb, err := ioutil.ReadAll(r) if err != nil { return err } - - f.Write(bb) + if _, err := f.Write(bb); err != nil { + return err + } return nil } diff --git a/internal/resource/base.go b/internal/resource/base.go index 1dd56c35..0d28be79 100644 --- a/internal/resource/base.go +++ b/internal/resource/base.go @@ -163,8 +163,11 @@ func (*Base) marshalObject(o runtime.Object) (string, error) { } func (b *Base) podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts LogOptions) error { - i := ctx.Value(IKey("informer")).(*watch.Informer) - pods, err := i.List(watch.PodIndex, opts.Namespace, metav1.ListOptions{ + inf, ok := ctx.Value(IKey("informer")).(*watch.Informer) + if !ok { + return errors.New("Expecting valid informer") + } + pods, err := inf.List(watch.PodIndex, opts.Namespace, metav1.ListOptions{ LabelSelector: toSelector(sel), }) if err != nil { @@ -176,7 +179,10 @@ func (b *Base) podLogs(ctx context.Context, c chan<- string, sel map[string]stri } pr := NewPod(b.Connection) for _, p := range pods { - po := p.(*v1.Pod) + po, ok := p.(*v1.Pod) + if !ok { + return errors.New("Expecting valid pod") + } if po.Status.Phase == v1.PodRunning { opts.Namespace, opts.Name = po.Namespace, po.Name if err := pr.PodLogs(ctx, c, opts); err != nil { diff --git a/internal/resource/container.go b/internal/resource/container.go index 831e3258..0bc89b20 100644 --- a/internal/resource/container.go +++ b/internal/resource/container.go @@ -2,6 +2,7 @@ package resource import ( "context" + "errors" "fmt" "strconv" "strings" @@ -9,6 +10,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/derailed/k9s/internal/k8s" + "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) @@ -48,7 +50,12 @@ func NewContainer(c Connection, pod *v1.Pod) *Container { // New builds a new Container instance from a k8s resource. func (r *Container) New(i interface{}) Columnar { co := NewContainer(r.Connection, r.pod) - co.instance = i.(v1.Container) + coi, ok := i.(v1.Container) + if !ok { + log.Error().Err(errors.New("Expecting a container resource")) + return nil + } + co.instance = coi co.path = r.namespacedName(r.pod.ObjectMeta) + ":" + co.instance.Name return co @@ -228,9 +235,9 @@ func toState(s v1.ContainerState) string { if s.Terminated.Reason != "" { return s.Terminated.Reason } - return "Terminating" + return Terminating case s.Running != nil: - return "Running" + return Running default: return MissingValue } diff --git a/internal/resource/container_test.go b/internal/resource/container_test.go index 5e4458ed..2fb499b5 100644 --- a/internal/resource/container_test.go +++ b/internal/resource/container_test.go @@ -17,7 +17,8 @@ func TestProbe(t *testing.T) { "undefined": {nil, "off"}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, probe(u.probe)) }) @@ -34,7 +35,8 @@ func TestAsMi(t *testing.T) { "10Mb": {10 * 1024 * 1024, 1.048576e+07}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, asMi(u.mem)) }) @@ -55,7 +57,8 @@ func TestToRes(t *testing.T) { "0", "0"}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { cpu, mem := toRes(u.res) assert.Equal(t, u.ecpu, cpu) @@ -93,7 +96,8 @@ func TestToState(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, toState(u.state)) }) diff --git a/internal/resource/context_test.go b/internal/resource/context_test.go index ed63a252..053785ba 100644 --- a/internal/resource/context_test.go +++ b/internal/resource/context_test.go @@ -120,14 +120,6 @@ func k8sConfig() *k8s.Config { return k8s.NewConfig(&f) } -func k8sCTX() *api.Context { - return &api.Context{ - LocationOfOrigin: "fred", - Cluster: "blee", - AuthInfo: "secret", - } -} - func k8sNamedCTX() *k8s.NamedContext { return k8s.NewNamedContext( k8sConfig(), diff --git a/internal/resource/cr.go b/internal/resource/cr.go index 02caa399..48f2d593 100644 --- a/internal/resource/cr.go +++ b/internal/resource/cr.go @@ -1,6 +1,8 @@ package resource import ( + "errors" + "github.com/derailed/k9s/internal/k8s" "github.com/rs/zerolog/log" v1 "k8s.io/api/rbac/v1" @@ -54,7 +56,10 @@ func (r *ClusterRole) Marshal(path string) (string, error) { return "", err } - cr := i.(*v1.ClusterRole) + cr, ok := i.(*v1.ClusterRole) + if !ok { + return "", errors.New("Expecting a cr resource") + } cr.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" cr.TypeMeta.Kind = "ClusterRole" diff --git a/internal/resource/cr_binding.go b/internal/resource/cr_binding.go index 889fcf8a..c810932a 100644 --- a/internal/resource/cr_binding.go +++ b/internal/resource/cr_binding.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "strings" "github.com/derailed/k9s/internal/k8s" @@ -56,7 +57,10 @@ func (r *ClusterRoleBinding) Marshal(path string) (string, error) { return "", err } - crb := i.(*v1.ClusterRoleBinding) + crb, ok := i.(*v1.ClusterRoleBinding) + if !ok { + return "", errors.New("Expecting a crb resource") + } crb.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" crb.TypeMeta.Kind = "ClusterRoleBinding" diff --git a/internal/resource/cr_binding_test.go b/internal/resource/cr_binding_test.go index c614257b..fa27aec3 100644 --- a/internal/resource/cr_binding_test.go +++ b/internal/resource/cr_binding_test.go @@ -73,7 +73,7 @@ func k8sCRB() *rbacv1.ClusterRoleBinding { ObjectMeta: metav1.ObjectMeta{ Name: "fred", Namespace: "blee", - CreationTimestamp: metav1.Time{testTime()}, + CreationTimestamp: metav1.Time{Time: testTime()}, }, Subjects: []rbacv1.Subject{ {Kind: "test", Name: "fred", Namespace: "blee"}, diff --git a/internal/resource/cr_test.go b/internal/resource/cr_test.go index 81ceec41..11a55a2b 100644 --- a/internal/resource/cr_test.go +++ b/internal/resource/cr_test.go @@ -94,7 +94,7 @@ func k8sCR() *rbacv1.ClusterRole { ObjectMeta: metav1.ObjectMeta{ Name: "fred", Namespace: "blee", - CreationTimestamp: metav1.Time{testTime()}, + CreationTimestamp: metav1.Time{Time: testTime()}, }, Rules: []rbacv1.PolicyRule{ { diff --git a/internal/resource/crd.go b/internal/resource/crd.go index e7e09608..de75d576 100644 --- a/internal/resource/crd.go +++ b/internal/resource/crd.go @@ -48,8 +48,16 @@ func (r *CustomResourceDefinition) New(i interface{}) Columnar { default: log.Fatal().Msgf("unknown CustomResourceDefinition type %#v", i) } - meta := c.instance.Object["metadata"].(map[string]interface{}) - c.path = meta["name"].(string) + meta, ok := c.instance.Object["metadata"].(map[string]interface{}) + if !ok { + log.Error().Err(errors.New("Expecting a map interface")).Msg("CRD New") + return nil + } + c.path, ok = meta["name"].(string) + if !ok { + log.Error().Err(errors.New("Expecting a string name")).Msg("CRD New") + return nil + } return c } @@ -82,13 +90,16 @@ func (r *CustomResourceDefinition) Fields(ns string) Row { ff := make(Row, 0, len(r.Header(ns))) i := r.instance - meta := i.Object["metadata"].(map[string]interface{}) + meta, ok := i.Object["metadata"].(map[string]interface{}) + if !ok { + log.Fatal().Err(errors.New("Expecting a map interface")).Msg("CRD Fields") + } t, err := time.Parse(time.RFC3339, meta["creationTimestamp"].(string)) if err != nil { log.Error().Msgf("Fields timestamp %v", err) } - return append(ff, meta["name"].(string), toAge(metav1.Time{t})) + return append(ff, meta["name"].(string), toAge(metav1.Time{Time: t})) } // ExtFields returns extended fields. @@ -97,20 +108,33 @@ func (r *CustomResourceDefinition) ExtFields() (TypeMeta, error) { i := r.instance spec, ok := i.Object["spec"].(map[string]interface{}) if !ok { - return m, errors.New("missing crd specs") + return m, errors.New("expecting interface map spec") } - if meta, ok := i.Object["metadata"].(map[string]interface{}); ok { - m.Name = meta["name"].(string) + if meta, k := i.Object["metadata"].(map[string]interface{}); k { + m.Name, ok = meta["name"].(string) + if !ok { + return m, errors.New("expecting meta string name") + } } m.Group, m.Version = spec["group"].(string), spec["version"].(string) m.Namespaced = isNamespaced(spec["scope"].(string)) names, ok := spec["names"].(map[string]interface{}) if !ok { - return m, errors.New("missing crd names") + return m, errors.New("expecting crd interface map names") + } + m.Kind, ok = names["kind"].(string) + if !ok { + return m, errors.New("expecting string kind") + } + m.Singular, ok = names["singular"].(string) + if !ok { + return m, errors.New("expecting string singular") + } + m.Plural, ok = names["plural"].(string) + if !ok { + return m, errors.New("expecting string plural") } - m.Kind = names["kind"].(string) - m.Singular, m.Plural = names["singular"].(string), names["plural"].(string) if names["shortNames"] != nil { for _, s := range names["shortNames"].([]interface{}) { m.ShortNames = append(m.ShortNames, s.(string)) diff --git a/internal/resource/crd_test.go b/internal/resource/crd_test.go index 74edbdc7..1b96436f 100644 --- a/internal/resource/crd_test.go +++ b/internal/resource/crd_test.go @@ -103,33 +103,6 @@ func k8sCRD() *unstructured.Unstructured { } } -func k8sCRDFull() *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "metadata": map[string]interface{}{ - "namespace": "blee", - "name": "fred", - "creationTimestamp": "2018-12-14T10:36:43.326972Z", - }, - "spec": map[string]interface{}{ - "group": "apps", - "version": "v1", - "names": map[string]interface{}{ - "kind": "cool", - "singular": "cool", - "plural": "cools", - "shortNamed": []string{"co", "cos"}, - }, - }, - }, - } -} - -func newCRDFull() resource.Columnar { - mc := NewMockConnection() - return resource.NewCustomResourceDefinition(mc).New(k8sCRDFull()) -} - func newCRD() resource.Columnar { mc := NewMockConnection() return resource.NewCustomResourceDefinition(mc).New(k8sCRD()) diff --git a/internal/resource/cronjob.go b/internal/resource/cronjob.go index d8192024..d7602fc0 100644 --- a/internal/resource/cronjob.go +++ b/internal/resource/cronjob.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "fmt" "strconv" @@ -69,7 +70,10 @@ func (r *CronJob) Marshal(path string) (string, error) { return "", err } - cj := i.(*batchv1beta1.CronJob) + cj, ok := i.(*batchv1beta1.CronJob) + if !ok { + return "", errors.New("expecting cronjob resource") + } cj.TypeMeta.APIVersion = "extensions/batchv1beta1" cj.TypeMeta.Kind = "CronJob" diff --git a/internal/resource/custom.go b/internal/resource/custom.go index dd23f8ff..58e29a00 100644 --- a/internal/resource/custom.go +++ b/internal/resource/custom.go @@ -48,6 +48,20 @@ func NewCustom(c k8s.Connection, gvr k8s.GVR) *Custom { return cr } +func mustExtractMeta(o map[string]interface{}) map[string]interface{} { + if m, ok := o["metadata"].(map[string]interface{}); ok { + return m + } + panic("unable to extract meta") +} + +func mustExtractStr(o map[string]interface{}, k string) string { + if s, ok := o[k].(string); ok { + return s + } + panic("unable to extract string for key `" + k) +} + // New builds a new Custom instance from a k8s resource. func (r *Custom) New(i interface{}) Columnar { cr := NewCustom(r.Connection, "") @@ -64,14 +78,10 @@ func (r *Custom) New(i interface{}) Columnar { if err != nil { log.Error().Err(err) } - meta := obj["metadata"].(map[string]interface{}) - ns := "" - if n, ok := meta["namespace"]; ok { - ns = n.(string) - } - name := meta["name"].(string) - cr.path = path.Join(ns, name) - cr.gvr = k8s.NewGVR(obj["kind"].(string), obj["apiVersion"].(string), name) + meta := mustExtractMeta(obj) + ns, n := mustExtractStr(meta, "namespace"), mustExtractStr(meta, "name") + cr.path = path.Join(ns, n) + cr.gvr = k8s.NewGVR(obj["kind"].(string), obj["apiVersion"].(string), n) return cr } @@ -107,7 +117,10 @@ func (r *Custom) List(ns string, opts v1.ListOptions) (Columnars, error) { return Columnars{}, errors.New("no resources found") } - table := ii[0].(*metav1beta1.Table) + table, ok := ii[0].(*metav1beta1.Table) + if !ok { + return nil, errors.New("expecting a table resource") + } r.headers = make(Row, len(table.ColumnDefinitions)) for i, h := range table.ColumnDefinitions { r.headers[i] = h.Name @@ -146,9 +159,11 @@ func (r *Custom) Fields(ns string) Row { return Row{} } - meta := obj["metadata"].(map[string]interface{}) + meta, ok := obj["metadata"].(map[string]interface{}) + if !ok { + log.Fatal().Msg("expecting interface map meta") + } rns, ok := meta["namespace"].(string) - if ns == AllNamespaces { if ok { ff = append(ff, rns) diff --git a/internal/resource/dp.go b/internal/resource/dp.go index b1fb76c9..df951da5 100644 --- a/internal/resource/dp.go +++ b/internal/resource/dp.go @@ -2,6 +2,7 @@ package resource import ( "context" + "errors" "fmt" "strconv" @@ -62,7 +63,10 @@ func (r *Deployment) Marshal(path string) (string, error) { return "", err } - dp := i.(*appsv1.Deployment) + dp, ok := i.(*appsv1.Deployment) + if !ok { + return "", errors.New("expecting dp resource") + } dp.TypeMeta.APIVersion = "apps/v1" dp.TypeMeta.Kind = "Deployment" @@ -75,7 +79,10 @@ func (r *Deployment) Logs(ctx context.Context, c chan<- string, opts LogOptions) if err != nil { return err } - dp := instance.(*appsv1.Deployment) + dp, ok := instance.(*appsv1.Deployment) + if !ok { + return errors.New("Expecting valid deployment") + } if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 { return fmt.Errorf("No valid selector found on deployment %s", opts.Name) } diff --git a/internal/resource/ds.go b/internal/resource/ds.go index 31c40d7e..fa318722 100644 --- a/internal/resource/ds.go +++ b/internal/resource/ds.go @@ -2,6 +2,7 @@ package resource import ( "context" + "errors" "fmt" "strconv" @@ -61,7 +62,10 @@ func (r *DaemonSet) Marshal(path string) (string, error) { return "", err } - ds := i.(*appsv1.DaemonSet) + ds, ok := i.(*appsv1.DaemonSet) + if !ok { + return "", errors.New("expecting ds resource") + } ds.TypeMeta.APIVersion = "apps/v1" ds.TypeMeta.Kind = "DaemonSet" @@ -75,7 +79,10 @@ func (r *DaemonSet) Logs(ctx context.Context, c chan<- string, opts LogOptions) return err } - ds := instance.(*appsv1.DaemonSet) + ds, ok := instance.(*appsv1.DaemonSet) + if !ok { + return errors.New("expecting ds resource") + } if ds.Spec.Selector == nil || len(ds.Spec.Selector.MatchLabels) == 0 { return fmt.Errorf("No valid selector found on daemonset %s", opts.FQN()) } diff --git a/internal/resource/ep.go b/internal/resource/ep.go index 2cbefff0..8124f0d2 100644 --- a/internal/resource/ep.go +++ b/internal/resource/ep.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "strconv" "strings" @@ -57,7 +58,10 @@ func (r *Endpoints) Marshal(path string) (string, error) { return "", err } - ep := i.(*v1.Endpoints) + ep, ok := i.(*v1.Endpoints) + if !ok { + return "", errors.New("expecting ep resource") + } ep.TypeMeta.APIVersion = "v1" ep.TypeMeta.Kind = "Endpoint" diff --git a/internal/resource/ep_test.go b/internal/resource/ep_test.go index 9ff3eb83..9d1d3151 100644 --- a/internal/resource/ep_test.go +++ b/internal/resource/ep_test.go @@ -97,11 +97,6 @@ func k8sEndpoints() *v1.Endpoints { } } -func newEndpoints() resource.Columnar { - mc := NewMockConnection() - return resource.NewEndpoints(mc).New(k8sEndpoints()) -} - func epYaml() string { return `apiVersion: v1 kind: Endpoint diff --git a/internal/resource/evt.go b/internal/resource/evt.go index 41c3d3f6..bf14e42d 100644 --- a/internal/resource/evt.go +++ b/internal/resource/evt.go @@ -1,7 +1,7 @@ package resource import ( - "regexp" + "errors" "strconv" "github.com/derailed/k9s/internal/k8s" @@ -57,7 +57,10 @@ func (r *Event) Marshal(path string) (string, error) { return "", err } - ev := i.(*v1.Event) + ev, ok := i.(*v1.Event) + if !ok { + return "", errors.New("expecting evt resource") + } ev.TypeMeta.APIVersion = "v1" ev.TypeMeta.Kind = "Event" @@ -79,8 +82,6 @@ func (*Event) Header(ns string) Row { return append(ff, "NAME", "REASON", "SOURCE", "COUNT", "MESSAGE", "AGE") } -var rx = regexp.MustCompile(`(.+)\.(.+)`) - // Fields returns display fields. func (r *Event) Fields(ns string) Row { ff := make(Row, 0, len(r.Header(ns))) @@ -99,29 +100,3 @@ func (r *Event) Fields(ns string) Row { toAge(i.LastTimestamp), ) } - -// ---------------------------------------------------------------------------- -// Helpers... - -func (*Event) toEmoji(t, r string) string { - switch t { - case "Warning": - switch r { - case "Failed": - return "😡" - case "Killing": - return "👿" - default: - return "😡" - } - default: - switch r { - case "Killing": - return "👿" - case "BackOff": - return "👹" - default: - return "😮" - } - } -} diff --git a/internal/resource/helpers.go b/internal/resource/helpers.go index 4ae61fc4..17b58c73 100644 --- a/internal/resource/helpers.go +++ b/internal/resource/helpers.go @@ -33,6 +33,9 @@ const ( MissingValue = "" // NAValue indicates a value that does not pertain. NAValue = "n/a" + + // UnknownValue represents an unknown. + UnknownValue = "" ) // MetaFQN returns a fully qualified resource name. @@ -61,48 +64,16 @@ func toSelector(m map[string]string) string { return strings.Join(s, ",") } -func empty(s []string) bool { - for _, v := range s { - if len(v) != 0 { - return false - } - } - return true -} - // Join a slice of strings, skipping blanks. -func join(a []string, sep string) string { - switch len(a) { - case 0: - return "" - case 1: - return a[0] - } - - var b []string +func join(a []string) string { + ss := make([]string, 0, len(a)) for _, s := range a { if s != "" { - b = append(b, s) + ss = append(ss, s) } } - if len(b) == 0 { - return "" - } - n := len(sep) * (len(b) - 1) - for i := 0; i < len(b); i++ { - n += len(a[i]) - } - - var buff strings.Builder - buff.Grow(n) - buff.WriteString(a[0]) - for _, s := range b[1:] { - buff.WriteString(sep) - buff.WriteString(s) - } - - return buff.String() + return strings.Join(ss, ",") } // AsPerc prints a number as a percentage. @@ -141,10 +112,6 @@ func check(s, sub string) string { return s } -func intToStr(i int64) string { - return strconv.Itoa(int(i)) -} - func boolToStr(b bool) string { switch b { case true: @@ -161,7 +128,7 @@ func toAge(timestamp metav1.Time) string { func toAgeHuman(s string) string { d, err := time.ParseDuration(s) if err != nil { - return "" + return UnknownValue } return duration.HumanDuration(d) diff --git a/internal/resource/helpers_test.go b/internal/resource/helpers_test.go index 904ebaed..d6efc652 100644 --- a/internal/resource/helpers_test.go +++ b/internal/resource/helpers_test.go @@ -17,9 +17,10 @@ func TestJoin(t *testing.T) { "sparse": {[]string{"a", "", "c"}, "a,c"}, } - for k, v := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, v.e, join(v.i, ",")) + assert.Equal(t, u.e, join(u.i)) }) } } diff --git a/internal/resource/hpa_v1.go b/internal/resource/hpa_v1.go index ccbe1d3e..dd266fd8 100644 --- a/internal/resource/hpa_v1.go +++ b/internal/resource/hpa_v1.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "strconv" "github.com/derailed/k9s/internal/k8s" @@ -56,7 +57,10 @@ func (r *HorizontalPodAutoscalerV1) Marshal(path string) (string, error) { return "", err } - hpa := i.(*autoscalingv1.HorizontalPodAutoscaler) + hpa, ok := i.(*autoscalingv1.HorizontalPodAutoscaler) + if !ok { + return "", errors.New("expecting hpa resource") + } hpa.TypeMeta.APIVersion = extractVersion(hpa.Annotations) hpa.TypeMeta.Kind = "HorizontalPodAutoscaler" @@ -104,12 +108,12 @@ func (r *HorizontalPodAutoscalerV1) Fields(ns string) Row { // Helpers... func (r *HorizontalPodAutoscalerV1) toMetrics(spec autoscalingv1.HorizontalPodAutoscalerSpec, status autoscalingv1.HorizontalPodAutoscalerStatus) string { - current := "" + current := UnknownValue if status.CurrentCPUUtilizationPercentage != nil { current = strconv.Itoa(int(*status.CurrentCPUUtilizationPercentage)) + "%" } - target := "" + target := UnknownValue if spec.TargetCPUUtilizationPercentage != nil { target = strconv.Itoa(int(*spec.TargetCPUUtilizationPercentage)) } diff --git a/internal/resource/hpa_v2beta1.go b/internal/resource/hpa_v2beta1.go index faa16883..3d838277 100644 --- a/internal/resource/hpa_v2beta1.go +++ b/internal/resource/hpa_v2beta1.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "strconv" "strings" @@ -57,7 +58,10 @@ func (r *HorizontalPodAutoscalerV2Beta1) Marshal(path string) (string, error) { return "", err } - hpa := i.(*autoscalingv2beta1.HorizontalPodAutoscaler) + hpa, ok := i.(*autoscalingv2beta1.HorizontalPodAutoscaler) + if !ok { + return "", errors.New("expecting hpa resource") + } hpa.TypeMeta.APIVersion = extractVersion(hpa.Annotations) hpa.TypeMeta.Kind = "HorizontalPodAutoscaler" @@ -129,7 +133,7 @@ func (r *HorizontalPodAutoscalerV2Beta1) toMetrics(specs []autoscalingv2beta1.Me } func (r *HorizontalPodAutoscalerV2Beta1) checkHPAType(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string { - current := "" + current := UnknownValue switch spec.Type { case autoscalingv2beta1.ExternalMetricSourceType: @@ -152,7 +156,7 @@ func (r *HorizontalPodAutoscalerV2Beta1) checkHPAType(i int, spec autoscalingv2b } func (*HorizontalPodAutoscalerV2Beta1) externalMetrics(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string { - current := "" + current := UnknownValue if spec.External.TargetAverageValue != nil { if len(statuses) > i && statuses[i].External != nil && &statuses[i].External.CurrentAverageValue != nil { current = statuses[i].External.CurrentAverageValue.String() @@ -167,7 +171,7 @@ func (*HorizontalPodAutoscalerV2Beta1) externalMetrics(i int, spec autoscalingv2 } func (*HorizontalPodAutoscalerV2Beta1) resourceMetrics(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string { - current := "" + current := UnknownValue if status := checkTargetMetrics(i, spec, statuses); status != "" { return status diff --git a/internal/resource/hpa_v2beta2.go b/internal/resource/hpa_v2beta2.go index f837da4a..14c9cec2 100644 --- a/internal/resource/hpa_v2beta2.go +++ b/internal/resource/hpa_v2beta2.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "regexp" "strconv" "strings" @@ -58,7 +59,10 @@ func (r *HorizontalPodAutoscaler) Marshal(path string) (string, error) { return "", err } - hpa := i.(*autoscalingv2beta2.HorizontalPodAutoscaler) + hpa, ok := i.(*autoscalingv2beta2.HorizontalPodAutoscaler) + if !ok { + return "", errors.New("expecting hpa resource") + } hpa.TypeMeta.APIVersion = extractVersion(hpa.Annotations) hpa.TypeMeta.Kind = "HorizontalPodAutoscaler" @@ -116,6 +120,20 @@ func (r *HorizontalPodAutoscaler) Fields(ns string) Row { // ---------------------------------------------------------------------------- // Helpers... +func computePodStatus(ss []autoscalingv2beta2.MetricStatus, index int, current string) string { + if len(ss) > index && ss[index].Pods != nil { + return ss[index].Pods.Current.AverageValue.String() + } + return current +} + +func computeObjectStatus(ss []autoscalingv2beta2.MetricStatus, index int, current string) string { + if len(ss) > index && ss[index].Object != nil { + return ss[index].Object.Current.Value.String() + } + return current +} + func toMetrics(specs []autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string { if len(specs) == 0 { return MissingValue @@ -123,21 +141,15 @@ func toMetrics(specs []autoscalingv2beta2.MetricSpec, statuses []autoscalingv2be list, max, more, count := []string{}, 2, false, 0 for i, spec := range specs { - current := "" + current := UnknownValue switch spec.Type { case autoscalingv2beta2.ExternalMetricSourceType: list = append(list, externalMetrics(i, spec, statuses)) case autoscalingv2beta2.PodsMetricSourceType: - if len(statuses) > i && statuses[i].Pods != nil { - current = statuses[i].Pods.Current.AverageValue.String() - } - list = append(list, current+"/"+spec.Pods.Target.AverageValue.String()) + list = append(list, computePodStatus(statuses, i, current)+"/"+spec.Pods.Target.AverageValue.String()) case autoscalingv2beta2.ObjectMetricSourceType: - if len(statuses) > i && statuses[i].Object != nil { - current = statuses[i].Object.Current.Value.String() - } - list = append(list, current+"/"+spec.Object.Target.Value.String()) + list = append(list, computeObjectStatus(statuses, i, current)+"/"+spec.Object.Target.Value.String()) case autoscalingv2beta2.ResourceMetricSourceType: list = append(list, resourceMetrics(i, spec, statuses)) default: @@ -159,7 +171,7 @@ func toMetrics(specs []autoscalingv2beta2.MetricSpec, statuses []autoscalingv2be } func externalMetrics(i int, spec autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string { - current := "" + current := UnknownValue if spec.External.Target.AverageValue != nil { if len(statuses) > i && statuses[i].External != nil && &statuses[i].External.Current.AverageValue != nil { @@ -175,7 +187,7 @@ func externalMetrics(i int, spec autoscalingv2beta2.MetricSpec, statuses []autos } func resourceMetrics(i int, spec autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string { - current := "" + current := UnknownValue if spec.Resource.Target.AverageValue != nil { if len(statuses) > i && statuses[i].Resource != nil { diff --git a/internal/resource/ing.go b/internal/resource/ing.go index a5ca3e1f..66a038b6 100644 --- a/internal/resource/ing.go +++ b/internal/resource/ing.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "strings" "github.com/derailed/k9s/internal/k8s" @@ -57,7 +58,10 @@ func (r *Ingress) Marshal(path string) (string, error) { return "", err } - ing := i.(*v1beta1.Ingress) + ing, ok := i.(*v1beta1.Ingress) + if !ok { + return "", errors.New("expecting ing resource") + } ing.TypeMeta.APIVersion = "extensions/v1beta1" ing.TypeMeta.Kind = "Ingress" diff --git a/internal/resource/job.go b/internal/resource/job.go index 0104edae..e7f9adb8 100644 --- a/internal/resource/job.go +++ b/internal/resource/job.go @@ -2,10 +2,10 @@ package resource import ( "context" + "errors" "fmt" "strconv" "strings" - "sync" "time" "github.com/derailed/k9s/internal/k8s" @@ -20,7 +20,6 @@ type Job struct { *Base instance *batchv1.Job - mx sync.RWMutex } // NewJobList returns a new resource list. @@ -67,7 +66,10 @@ func (r *Job) Marshal(path string) (string, error) { return "", err } - jo := i.(*batchv1.Job) + jo, ok := i.(*batchv1.Job) + if !ok { + return "", errors.New("expecting job resource") + } jo.TypeMeta.APIVersion = "extensions/v1beta1" jo.TypeMeta.Kind = "Job" @@ -87,7 +89,10 @@ func (r *Job) Logs(ctx context.Context, c chan<- string, opts LogOptions) error if err != nil { return err } - jo := instance.(*batchv1.Job) + jo, ok := instance.(*batchv1.Job) + if !ok { + return errors.New("expecting job resource") + } if jo.Spec.Selector == nil || len(jo.Spec.Selector.MatchLabels) == 0 { return fmt.Errorf("No valid selector found on job %s", opts.FQN()) } diff --git a/internal/resource/job_int_test.go b/internal/resource/job_int_test.go index a8de3436..51ebc252 100644 --- a/internal/resource/job_int_test.go +++ b/internal/resource/job_int_test.go @@ -12,7 +12,7 @@ import ( func TestJobToCompletion(t *testing.T) { t0 := testTime() - t1, t2 := metav1.Time{t0}, metav1.Time{t0.Add(10 * time.Second)} + t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)} var c, p int32 = 10, 20 uu := []struct { @@ -81,7 +81,7 @@ func TestJobToCompletion(t *testing.T) { func TestJobToDuration(t *testing.T) { t0 := testTime().UTC() - t1, t2 := metav1.Time{t0}, metav1.Time{t0.Add(10 * time.Second)} + t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)} uu := []struct { s batchv1.JobStatus @@ -96,7 +96,7 @@ func TestJobToDuration(t *testing.T) { }, { batchv1.JobStatus{ - StartTime: &metav1.Time{time.Now().Add(-10 * time.Second)}, + StartTime: &metav1.Time{Time: time.Now().Add(-10 * time.Second)}, }, "10s", }, diff --git a/internal/resource/list.go b/internal/resource/list.go index 00140246..38d03721 100644 --- a/internal/resource/list.go +++ b/internal/resource/list.go @@ -254,32 +254,34 @@ func (l *list) load(informer *wa.Informer, ns string) (Columnars, error) { } func (l *list) fetchResource(informer *wa.Informer, r interface{}, ns string) (Columnar, error) { - var err error - res := l.resource.New(r) + switch o := r.(type) { case *v1.Node: fqn := MetaFQN(o.ObjectMeta) - nmx, err := informer.Get(wa.NodeMXIndex, fqn, metav1.GetOptions{}) - if err == nil { + if nmx, err := informer.Get(wa.NodeMXIndex, fqn, metav1.GetOptions{}); err != nil { + return res, err + } else { res.SetNodeMetrics(nmx.(*mv1beta1.NodeMetrics)) } case *v1.Pod: fqn := MetaFQN(o.ObjectMeta) - pmx, err := informer.Get(wa.PodMXIndex, fqn, metav1.GetOptions{}) - if err == nil { + if pmx, err := informer.Get(wa.PodMXIndex, fqn, metav1.GetOptions{}); err != nil { + return res, err + } else { res.SetPodMetrics(pmx.(*mv1beta1.PodMetrics)) } case v1.Container: - pmx, err := informer.Get(wa.PodMXIndex, ns, metav1.GetOptions{}) - if err == nil { + if pmx, err := informer.Get(wa.PodMXIndex, ns, metav1.GetOptions{}); err != nil { + return res, err + } else { res.SetPodMetrics(pmx.(*mv1beta1.PodMetrics)) } default: - err = fmt.Errorf("No informer matched %s:%s", l.name, ns) + return res, fmt.Errorf("No informer matched %s:%s", l.name, ns) } - return res, err + return res, nil } // Reconcile previous vs current state and emits delta events. diff --git a/internal/resource/log_options.go b/internal/resource/log_options.go index f96ab362..37807514 100644 --- a/internal/resource/log_options.go +++ b/internal/resource/log_options.go @@ -17,8 +17,8 @@ type ( Fqn Lines int64 - Previous bool Color color.Paint + Previous bool SingleContainer bool MultiPods bool } diff --git a/internal/resource/no.go b/internal/resource/no.go index fdf4a122..e979c09e 100644 --- a/internal/resource/no.go +++ b/internal/resource/no.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "strings" "k8s.io/apimachinery/pkg/util/sets" @@ -77,8 +78,14 @@ func (r *Node) List(ns string, opts metav1.ListOptions) (Columnars, error) { cc := make(Columnars, 0, len(nn)) for i := range nn { - node := nn[i].(v1.Node) - no := r.New(&node).(*Node) + node, ok := nn[i].(v1.Node) + if !ok { + return nil, errors.New("Expecting a node resource") + } + no, ok := r.New(&node).(*Node) + if !ok { + return nil, errors.New("Expecting a node resource") + } cc = append(cc, no) } @@ -94,7 +101,10 @@ func (r *Node) Marshal(path string) (string, error) { return "", err } - no := i.(*v1.Node) + no, ok := i.(*v1.Node) + if !ok { + return "", errors.New("Expecting a node resource") + } no.TypeMeta.APIVersion = "v1" no.TypeMeta.Kind = "Node" @@ -150,8 +160,8 @@ func (r *Node) Fields(ns string) Row { return append(ff, no.Name, - join(sta, ","), - join(ro.List(), ","), + join(sta), + join(ro.List()), no.Status.NodeInfo.KubeletVersion, no.Status.NodeInfo.KernelVersion, iIP, @@ -205,7 +215,7 @@ func gatherNodeMX(no *v1.Node, mx *mv1beta1.NodeMetrics) (c metric, a metric, p return } -func (_ *Node) findNodeRoles(no *v1.Node, roles *sets.String) []string { +func (_ *Node) findNodeRoles(no *v1.Node, roles *sets.String) { for k, v := range no.Labels { switch { case strings.HasPrefix(k, labelNodeRolePrefix): @@ -220,8 +230,6 @@ func (_ *Node) findNodeRoles(no *v1.Node, roles *sets.String) []string { if roles.Len() == 0 { roles.Insert(MissingValue) } - - return roles.List() } func (*Node) getIPs(addrs []v1.NodeAddress) (iIP, eIP string) { @@ -268,71 +276,72 @@ func (*Node) status(status v1.NodeStatus, exempt bool, res []string) { } } -func (r *Node) podsResources(name string) (v1.ResourceList, v1.ResourceList, error) { - reqs, limits := v1.ResourceList{}, v1.ResourceList{} - pods, err := r.Connection.NodePods(name) - if err != nil { - return reqs, limits, err - } - for _, p := range pods.Items { - preq, plim := podResources(&p) - for k, v := range preq { - if value, ok := reqs[k]; !ok { - reqs[k] = v.DeepCopy() - } else { - value.Add(v) - reqs[k] = value - } - } - for k, v := range plim { - if value, ok := limits[k]; !ok { - limits[k] = v.DeepCopy() - } else { - value.Add(v) - limits[k] = value - } - } - } +// BOZO!! +// func (r *Node) podsResources(name string) (v1.ResourceList, v1.ResourceList, error) { +// reqs, limits := v1.ResourceList{}, v1.ResourceList{} +// pods, err := r.Connection.NodePods(name) +// if err != nil { +// return reqs, limits, err +// } +// for _, p := range pods.Items { +// preq, plim := podResources(&p) +// for k, v := range preq { +// if value, ok := reqs[k]; !ok { +// reqs[k] = v.DeepCopy() +// } else { +// value.Add(v) +// reqs[k] = value +// } +// } +// for k, v := range plim { +// if value, ok := limits[k]; !ok { +// limits[k] = v.DeepCopy() +// } else { +// value.Add(v) +// limits[k] = value +// } +// } +// } - return reqs, limits, nil -} +// return reqs, limits, nil +// } -func podResources(pod *v1.Pod) (v1.ResourceList, v1.ResourceList) { - reqs, limits := v1.ResourceList{}, v1.ResourceList{} - for _, container := range pod.Spec.Containers { - addResources(reqs, container.Resources.Requests) - addResources(limits, container.Resources.Limits) - } - // init containers define the minimum of any resource - for _, container := range pod.Spec.InitContainers { - maxResources(reqs, container.Resources.Requests) - maxResources(limits, container.Resources.Limits) - } +// func podResources(pod *v1.Pod) (v1.ResourceList, v1.ResourceList) { +// reqs, limits := v1.ResourceList{}, v1.ResourceList{} +// for _, container := range pod.Spec.Containers { +// addResources(reqs, container.Resources.Requests) +// addResources(limits, container.Resources.Limits) +// } +// // init containers define the minimum of any resource +// for _, container := range pod.Spec.InitContainers { +// maxResources(reqs, container.Resources.Requests) +// maxResources(limits, container.Resources.Limits) +// } - return reqs, limits -} +// return reqs, limits +// } -// AddResources adds the resources from l2 to l1. -func addResources(l1, l2 v1.ResourceList) { - for name, quantity := range l2 { - if value, ok := l1[name]; ok { - value.Add(quantity) - l1[name] = value - } else { - l1[name] = quantity.DeepCopy() - } - } -} +// // AddResources adds the resources from l2 to l1. +// func addResources(l1, l2 v1.ResourceList) { +// for name, quantity := range l2 { +// if value, ok := l1[name]; ok { +// value.Add(quantity) +// l1[name] = value +// } else { +// l1[name] = quantity.DeepCopy() +// } +// } +// } -// MaxResourceList sets list to the greater of l1/l2 for every resource. -func maxResources(l1, l2 v1.ResourceList) { - for name, quantity := range l2 { - if value, ok := l1[name]; ok { - if quantity.Cmp(value) > 0 { - l1[name] = quantity.DeepCopy() - } - } else { - l1[name] = quantity.DeepCopy() - } - } -} +// // MaxResourceList sets list to the greater of l1/l2 for every resource. +// func maxResources(l1, l2 v1.ResourceList) { +// for name, quantity := range l2 { +// if value, ok := l1[name]; ok { +// if quantity.Cmp(value) > 0 { +// l1[name] = quantity.DeepCopy() +// } +// } else { +// l1[name] = quantity.DeepCopy() +// } +// } +// } diff --git a/internal/resource/no_int_test.go b/internal/resource/no_int_test.go index c6e9460c..91b40f0d 100644 --- a/internal/resource/no_int_test.go +++ b/internal/resource/no_int_test.go @@ -32,7 +32,7 @@ func TestNodeStatus(t *testing.T) { for _, u := range uu { res := make([]string, 5) no.status(u.s, false, res) - assert.Equal(t, "Ready", join(res, ",")) + assert.Equal(t, "Ready", join(res)) } } diff --git a/internal/resource/np.go b/internal/resource/np.go index 14e33281..a7d3fd18 100644 --- a/internal/resource/np.go +++ b/internal/resource/np.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "fmt" "strings" @@ -58,7 +59,10 @@ func (r *NetworkPolicy) Marshal(path string) (string, error) { return "", err } - ds := i.(*networkingv1.NetworkPolicy) + ds, ok := i.(*networkingv1.NetworkPolicy) + if !ok { + return "", errors.New("Expecting a np resource") + } ds.TypeMeta.APIVersion = "networking.k8s.io/v1" ds.TypeMeta.Kind = "NetworkPolicy" diff --git a/internal/resource/ns.go b/internal/resource/ns.go index 19335a79..bfdb9e19 100644 --- a/internal/resource/ns.go +++ b/internal/resource/ns.go @@ -1,6 +1,8 @@ package resource import ( + "errors" + "github.com/derailed/k9s/internal/k8s" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" @@ -55,7 +57,10 @@ func (r *Namespace) Marshal(path string) (string, error) { return "", err } - nss := i.(*v1.Namespace) + nss, ok := i.(*v1.Namespace) + if !ok { + return "", errors.New("Expecting a ns resource") + } nss.TypeMeta.APIVersion = "v1" nss.TypeMeta.Kind = "Namespace" diff --git a/internal/resource/pdb.go b/internal/resource/pdb.go index a7957f3f..66d7c1ea 100644 --- a/internal/resource/pdb.go +++ b/internal/resource/pdb.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "strconv" "github.com/derailed/k9s/internal/k8s" @@ -44,7 +45,10 @@ func (r *PodDisruptionBudget) New(i interface{}) Columnar { c.instance = &instance case *interface{}: ptr := *i.(*interface{}) - pdbi := ptr.(v1beta1.PodDisruptionBudget) + pdbi, ok := ptr.(v1beta1.PodDisruptionBudget) + if !ok { + log.Fatal().Msg("Expecting a pdb resource") + } c.instance = &pdbi default: log.Fatal().Msgf("unknown PDB type %#v", i) @@ -62,7 +66,10 @@ func (r *PodDisruptionBudget) Marshal(path string) (string, error) { return "", err } - pdb := i.(*v1beta1.PodDisruptionBudget) + pdb, ok := i.(*v1beta1.PodDisruptionBudget) + if !ok { + return "", errors.New("Expecting a pdb resource") + } pdb.TypeMeta.APIVersion = "v1beta1" pdb.TypeMeta.Kind = "PodDisruptionBudget" diff --git a/internal/resource/pod.go b/internal/resource/pod.go index dd568e74..e2bba558 100644 --- a/internal/resource/pod.go +++ b/internal/resource/pod.go @@ -3,6 +3,7 @@ package resource import ( "bufio" "context" + "errors" "fmt" "io" "strconv" @@ -21,6 +22,10 @@ import ( const ( defaultTimeout = 1 * time.Second + Terminating = "Terminating" + Running = "Running" + Initialized = "Initialized" + Completed = "Completed" ) type ( @@ -81,7 +86,10 @@ func (r *Pod) New(i interface{}) Columnar { c.instance = &instance case *interface{}: ptr := *instance - po := ptr.(v1.Pod) + po, ok := ptr.(v1.Pod) + if !ok { + log.Fatal().Msgf("Expecting a pod resource") + } c.instance = &po default: log.Fatal().Msgf("unknown Pod type %#v", i) @@ -103,7 +111,10 @@ func (r *Pod) Marshal(path string) (string, error) { if err != nil { return "", err } - po := i.(*v1.Pod) + po, ok := i.(*v1.Pod) + if !ok { + return "", errors.New("Expecting a pod resource") + } po.TypeMeta.APIVersion = "v1" po.TypeMeta.Kind = "Pod" @@ -119,13 +130,19 @@ func (r *Pod) Containers(path string, includeInit bool) ([]string, error) { // PodLogs tail logs for all containers in a running Pod. func (r *Pod) PodLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - i := ctx.Value(IKey("informer")).(*watch.Informer) - p, err := i.Get(watch.PodIndex, opts.FQN(), metav1.GetOptions{}) + inf, ok := ctx.Value(IKey("informer")).(*watch.Informer) + if !ok { + return errors.New("Expecting an informer") + } + p, err := inf.Get(watch.PodIndex, opts.FQN(), metav1.GetOptions{}) if err != nil { return err } - po := p.(*v1.Pod) + po, ok := p.(*v1.Pod) + if !ok { + return errors.New("Expecting a pod resource") + } opts.Color = asColor(po.Name) if len(po.Spec.InitContainers)+len(po.Spec.Containers) == 1 { opts.SingleContainer = true @@ -192,19 +209,19 @@ func tailLogs(ctx context.Context, res k8s.Loggable, c chan<- string, opts LogOp } func logsTimeout(cancel context.CancelFunc, blocked *int32) { - select { - case <-time.After(defaultTimeout): - if atomic.LoadInt32(blocked) == 1 { - log.Debug().Msg("Timed out reading the log stream") - cancel() - } + <-time.After(defaultTimeout) + if atomic.LoadInt32(blocked) == 1 { + log.Debug().Msg("Timed out reading the log stream") + cancel() } } func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts LogOptions) { defer func() { log.Debug().Msgf(">>> Closing stream `%s", opts.Path()) - stream.Close() + if err := stream.Close(); err != nil { + log.Error().Err(err).Msg("Cloing stream") + } }() scanner := bufio.NewScanner(stream) @@ -227,7 +244,10 @@ func (r *Pod) List(ns string, opts metav1.ListOptions) (Columnars, error) { cc := make(Columnars, 0, len(pods)) for i := range pods { - po := r.New(&pods[i]).(*Pod) + po, ok := r.New(&pods[i]).(*Pod) + if !ok { + return nil, errors.New("Expecting a pod resource") + } cc = append(cc, po) } @@ -379,10 +399,6 @@ func (r *Pod) statuses(ss []v1.ContainerStatus) (cr, ct, rc int) { return } -func isSet(s *string) bool { - return s != nil && *s != "" -} - func (r *Pod) phase(po *v1.Pod) string { status := string(po.Status.Phase) if po.Status.Reason != "" { @@ -405,7 +421,7 @@ func (r *Pod) phase(po *v1.Pod) string { return status } - return "Terminating" + return Terminating } func (*Pod) containerPhase(st v1.PodStatus, status string) (bool, string) { @@ -433,11 +449,11 @@ func (*Pod) containerPhase(st v1.PodStatus, status string) (bool, string) { func (*Pod) initContainerPhase(st v1.PodStatus, initCount int, status string) (bool, string) { for i, cs := range st.InitContainerStatuses { - status := checkContainerStatus(cs, i, initCount) - if status == "" { + if state := checkContainerStatus(cs, i, initCount); state == "" { continue + } else { + return true, state } - return true, status } return false, status diff --git a/internal/resource/pod_int_test.go b/internal/resource/pod_int_test.go index b332bcc4..40f8714d 100644 --- a/internal/resource/pod_int_test.go +++ b/internal/resource/pod_int_test.go @@ -55,22 +55,22 @@ func TestPodPhase(t *testing.T) { e string }{ {makePodStatus("p1", v1.PodRunning, ""), "Running"}, - {makePodStatus("p1", v1.PodRunning, "Evicted"), "Evicted"}, + {makePodStatus("p2", v1.PodRunning, "Evicted"), "Evicted"}, {makePodStatus("p1", v1.PodPending, ""), "Pending"}, {makePodStatus("p1", v1.PodSucceeded, ""), "Succeeded"}, {makePodStatus("p1", v1.PodFailed, ""), "Failed"}, {makePodStatus("p1", v1.PodUnknown, ""), "Unknown"}, {makePodCoInitTerminated("p1"), "Init:OOMKilled"}, {makePodCoInitWaiting("p1", ""), "Init:0/1"}, - {makePodCoInitWaiting("p1", "Waiting"), "Init:Waiting"}, + {makePodCoInitWaiting("p2", "Waiting"), "Init:Waiting"}, {makePodCoInitWaiting("p1", "PodInitializing"), "Init:0/1"}, {makePodCoWaiting("p1", "Waiting"), "Waiting"}, {makePodCoWaiting("p1", ""), ""}, - {makePodCoTerminated("p1", "OOMKilled", 0, true), "Terminating"}, - {makePodCoTerminated("p1", "OOMKilled", 0, false), "OOMKilled"}, - {makePodCoTerminated("p1", "", 0, true), "Terminating"}, + {makePodCoTerminated("p1", "OOMKilled", 0, true), Terminating}, + {makePodCoTerminated("p2", "OOMKilled", 0, false), "OOMKilled"}, + {makePodCoTerminated("p1", "", 0, true), Terminating}, {makePodCoTerminated("p1", "", 0, false), "ExitCode:1"}, - {makePodCoTerminated("p1", "", 1, true), "Terminating"}, + {makePodCoTerminated("p1", "", 1, true), Terminating}, {makePodCoTerminated("p1", "", 1, false), "Signal:1"}, } @@ -127,7 +127,7 @@ func makePodCoTerminated(n, reason string, signal int32, deleted bool) *v1.Pod { po := makePod(n) if deleted { - po.DeletionTimestamp = &metav1.Time{time.Now()} + po.DeletionTimestamp = &metav1.Time{Time: time.Now()} } po.Status.ContainerStatuses = []v1.ContainerStatus{ { diff --git a/internal/resource/pod_test.go b/internal/resource/pod_test.go index e0e6231e..ce9f316f 100644 --- a/internal/resource/pod_test.go +++ b/internal/resource/pod_test.go @@ -55,7 +55,7 @@ func TestPodGatherMX(t *testing.T) { v1.ResourceRequirements{ Requests: makeRes("500m", "512Mi"), }, - makeMxPod("fred", "250m", "256Mi"), + makeMxPod("p1", "250m", "256Mi"), "150", "150", }, @@ -63,7 +63,7 @@ func TestPodGatherMX(t *testing.T) { v1.ResourceRequirements{ Limits: makeRes("1000m", "1024Mi"), }, - makeMxPod("fred", "250m", "256Mi"), + makeMxPod("p2", "250m", "256Mi"), "75", "75", }, @@ -72,13 +72,14 @@ func TestPodGatherMX(t *testing.T) { Requests: makeRes("500m", "512Mi"), Limits: makeRes("1000m", "1024Mi"), }, - makeMxPod("fred", "250m", "256Mi"), + makeMxPod("p3", "250m", "256Mi"), "150", "150", }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { r := NewPodWithMetrics(u.metrics, u.resources).Fields("blee") @@ -109,7 +110,7 @@ func TestPodListData(t *testing.T) { mx := NewMockMetricsServer() m.When(mx.HasMetrics()).ThenReturn(true) m.When(mx.FetchPodsMetrics("blee")). - ThenReturn(&mv1beta1.PodMetricsList{Items: []mv1beta1.PodMetrics{makeMxPod("fred", "100m", "20Mi")}}, nil) + ThenReturn(&mv1beta1.PodMetricsList{Items: []mv1beta1.PodMetrics{makeMxPod("p1", "100m", "20Mi")}}, nil) l := NewPodListWithArgs("blee", NewPodWithArgs(mc, mr, mx)) // Make sure we mcn get deltas! diff --git a/internal/resource/pv.go b/internal/resource/pv.go index 2701bb1c..6e150794 100644 --- a/internal/resource/pv.go +++ b/internal/resource/pv.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "path" "strings" @@ -57,7 +58,10 @@ func (r *PersistentVolume) Marshal(path string) (string, error) { return "", err } - pv := i.(*v1.PersistentVolume) + pv, ok := i.(*v1.PersistentVolume) + if !ok { + return "", errors.New("Expecting a pv resource") + } pv.TypeMeta.APIVersion = "v1" pv.TypeMeta.Kind = "PersistentVolume" @@ -84,7 +88,7 @@ func (r *PersistentVolume) Fields(ns string) Row { phase := i.Status.Phase if i.ObjectMeta.DeletionTimestamp != nil { - phase = "Terminating" + phase = Terminating } var claim string diff --git a/internal/resource/pvc.go b/internal/resource/pvc.go index 1dd97cc5..133d1c1d 100644 --- a/internal/resource/pvc.go +++ b/internal/resource/pvc.go @@ -1,6 +1,8 @@ package resource import ( + "errors" + "github.com/derailed/k9s/internal/k8s" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" @@ -54,7 +56,10 @@ func (r *PersistentVolumeClaim) Marshal(path string) (string, error) { return "", err } - pvc := i.(*v1.PersistentVolumeClaim) + pvc, ok := i.(*v1.PersistentVolumeClaim) + if !ok { + return "", errors.New("Expecting a pvc resource") + } pvc.TypeMeta.APIVersion = "v1" pvc.TypeMeta.Kind = "PersistentVolumeClaim" @@ -81,7 +86,7 @@ func (r *PersistentVolumeClaim) Fields(ns string) Row { phase := i.Status.Phase if i.ObjectMeta.DeletionTimestamp != nil { - phase = "Terminating" + phase = Terminating } var pv PersistentVolume diff --git a/internal/resource/rc.go b/internal/resource/rc.go index ec8f621f..0fdaa293 100644 --- a/internal/resource/rc.go +++ b/internal/resource/rc.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "strconv" "github.com/derailed/k9s/internal/k8s" @@ -56,7 +57,10 @@ func (r *ReplicationController) Marshal(path string) (string, error) { return "", err } - rc := i.(*v1.ReplicationController) + rc, ok := i.(*v1.ReplicationController) + if !ok { + return "", errors.New("Expecting a rc resource") + } rc.TypeMeta.APIVersion = "v1" rc.TypeMeta.Kind = "ReplicationController" diff --git a/internal/resource/ro.go b/internal/resource/ro.go index e515fb8f..4ed82c86 100644 --- a/internal/resource/ro.go +++ b/internal/resource/ro.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "strings" "github.com/derailed/k9s/internal/k8s" @@ -56,7 +57,10 @@ func (r *Role) Marshal(path string) (string, error) { return "", err } - role := i.(*v1.Role) + role, ok := i.(*v1.Role) + if !ok { + return "", errors.New("Expecting a role resource") + } role.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" role.TypeMeta.Kind = "Role" diff --git a/internal/resource/ro_binding.go b/internal/resource/ro_binding.go index bf665a46..29fa1dcd 100644 --- a/internal/resource/ro_binding.go +++ b/internal/resource/ro_binding.go @@ -1,6 +1,8 @@ package resource import ( + "errors" + "github.com/derailed/k9s/internal/k8s" "github.com/rs/zerolog/log" v1 "k8s.io/api/rbac/v1" @@ -54,7 +56,10 @@ func (r *RoleBinding) Marshal(path string) (string, error) { return "", err } - rb := i.(*v1.RoleBinding) + rb, ok := i.(*v1.RoleBinding) + if !ok { + return "", errors.New("Expecting a rb resource") + } rb.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" rb.TypeMeta.Kind = "RoleBinding" diff --git a/internal/resource/ro_binding_test.go b/internal/resource/ro_binding_test.go index 9c3d5c61..7ec39cdc 100644 --- a/internal/resource/ro_binding_test.go +++ b/internal/resource/ro_binding_test.go @@ -81,11 +81,6 @@ func k8sRB() *v1.RoleBinding { } } -func newRB() resource.Columnar { - mc := NewMockConnection() - return resource.NewRoleBinding(mc).New(k8sRB()) -} - func rbYaml() string { return `apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/internal/resource/ro_test.go b/internal/resource/ro_test.go index d362e57b..c38a40c8 100644 --- a/internal/resource/ro_test.go +++ b/internal/resource/ro_test.go @@ -69,11 +69,6 @@ func k8sRole() *v1.Role { } } -func newRole() resource.Columnar { - mc := NewMockConnection() - return resource.NewRole(mc).New(k8sRole()) -} - func roleYaml() string { return `apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/internal/resource/rs.go b/internal/resource/rs.go index 895936f2..04ccd9ef 100644 --- a/internal/resource/rs.go +++ b/internal/resource/rs.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "strconv" "github.com/derailed/k9s/internal/k8s" @@ -56,7 +57,10 @@ func (r *ReplicaSet) Marshal(path string) (string, error) { return "", err } - rs := i.(*v1.ReplicaSet) + rs, ok := i.(*v1.ReplicaSet) + if !ok { + return "", errors.New("Expecting a rs resource") + } rs.TypeMeta.APIVersion = "apps/v1" rs.TypeMeta.Kind = "ReplicaSet" diff --git a/internal/resource/rs_test.go b/internal/resource/rs_test.go index cec881dd..ecbf05f0 100644 --- a/internal/resource/rs_test.go +++ b/internal/resource/rs_test.go @@ -77,11 +77,6 @@ func k8sReplicaSet() *v1.ReplicaSet { } } -func newReplicaSet() resource.Columnar { - mc := NewMockConnection() - return resource.NewReplicaSet(mc).New(k8sReplicaSet()) -} - func rsYaml() string { return `apiVersion: apps/v1 kind: ReplicaSet diff --git a/internal/resource/sa.go b/internal/resource/sa.go index 61ebcb86..cac642ca 100644 --- a/internal/resource/sa.go +++ b/internal/resource/sa.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "strconv" "github.com/derailed/k9s/internal/k8s" @@ -56,7 +57,10 @@ func (r *ServiceAccount) Marshal(path string) (string, error) { return "", err } - sa := i.(*v1.ServiceAccount) + sa, ok := i.(*v1.ServiceAccount) + if !ok { + return "", errors.New("Expecting a sa resource") + } sa.TypeMeta.APIVersion = "v1" sa.TypeMeta.Kind = "ServiceAccount" diff --git a/internal/resource/sa_test.go b/internal/resource/sa_test.go index f2597374..0842714a 100644 --- a/internal/resource/sa_test.go +++ b/internal/resource/sa_test.go @@ -101,7 +101,7 @@ func k8sSA() *v1.ServiceAccount { ObjectMeta: metav1.ObjectMeta{ Name: "fred", Namespace: "blee", - CreationTimestamp: metav1.Time{testTime()}, + CreationTimestamp: metav1.Time{Time: testTime()}, }, Secrets: []v1.ObjectReference{{Name: "blee"}}, } diff --git a/internal/resource/sc.go b/internal/resource/sc.go index 8b547c34..f9302df0 100644 --- a/internal/resource/sc.go +++ b/internal/resource/sc.go @@ -1,6 +1,8 @@ package resource import ( + "errors" + "github.com/derailed/k9s/internal/k8s" "github.com/rs/zerolog/log" v1 "k8s.io/api/storage/v1" @@ -54,7 +56,10 @@ func (r *StorageClass) Marshal(path string) (string, error) { return "", err } - sc := i.(*v1.StorageClass) + sc, ok := i.(*v1.StorageClass) + if !ok { + return "", errors.New("Expecting a sc resource") + } sc.TypeMeta.APIVersion = "storage.k8s.io/v1" sc.TypeMeta.Kind = "StorageClass" diff --git a/internal/resource/sts.go b/internal/resource/sts.go index 72bb77e0..a62d16e9 100644 --- a/internal/resource/sts.go +++ b/internal/resource/sts.go @@ -2,6 +2,7 @@ package resource import ( "context" + "errors" "fmt" "strconv" @@ -62,7 +63,10 @@ func (r *StatefulSet) Marshal(path string) (string, error) { return "", err } - sts := i.(*appsv1.StatefulSet) + sts, ok := i.(*appsv1.StatefulSet) + if !ok { + return "", errors.New("Expecting an sts resource") + } sts.TypeMeta.APIVersion = "apps/v1" sts.TypeMeta.Kind = "StatefulSet" @@ -76,7 +80,10 @@ func (r *StatefulSet) Logs(ctx context.Context, c chan<- string, opts LogOptions return err } - sts := instance.(*appsv1.StatefulSet) + sts, ok := instance.(*appsv1.StatefulSet) + if !ok { + return errors.New("Expecting an sts resource") + } if sts.Spec.Selector == nil || len(sts.Spec.Selector.MatchLabels) == 0 { return fmt.Errorf("No valid selector found on statefulset %s", opts.FQN()) } diff --git a/internal/resource/sts_test.go b/internal/resource/sts_test.go index 199485f6..7db2e4ce 100644 --- a/internal/resource/sts_test.go +++ b/internal/resource/sts_test.go @@ -102,7 +102,7 @@ func k8sSTS() *v1.StatefulSet { ObjectMeta: metav1.ObjectMeta{ Name: "fred", Namespace: "blee", - CreationTimestamp: metav1.Time{testTime()}, + CreationTimestamp: metav1.Time{Time: testTime()}, }, Spec: v1.StatefulSetSpec{ Replicas: new(int32), diff --git a/internal/resource/svc.go b/internal/resource/svc.go index b0a8f81a..9358e0a2 100644 --- a/internal/resource/svc.go +++ b/internal/resource/svc.go @@ -12,8 +12,6 @@ import ( v1 "k8s.io/api/core/v1" ) -const lbIPWidth = 16 - // Service tracks a kubernetes resource. type Service struct { *Base @@ -63,7 +61,10 @@ func (r *Service) Marshal(path string) (string, error) { return "", err } - svc := i.(*v1.Service) + svc, ok := i.(*v1.Service) + if !ok { + return "", errors.New("Expecting a service resource") + } svc.TypeMeta.APIVersion = "v1" svc.TypeMeta.Kind = "Service" @@ -77,7 +78,10 @@ func (r *Service) Logs(ctx context.Context, c chan<- string, opts LogOptions) er return err } - svc := instance.(*v1.Service) + svc, ok := instance.(*v1.Service) + if !ok { + return errors.New("Expecting a service resource") + } log.Debug().Msgf("Service %s--%s", svc.Name, svc.Spec.Selector) if len(svc.Spec.Selector) == 0 { return errors.New("No logs for headless service") diff --git a/internal/resource/svc_int_test.go b/internal/resource/svc_int_test.go index 5bc1fdfe..ca83a760 100644 --- a/internal/resource/svc_int_test.go +++ b/internal/resource/svc_int_test.go @@ -22,8 +22,8 @@ func TestSvcExtIPs(t *testing.T) { func TestLbIngressIP(t *testing.T) { lb := v1.LoadBalancerStatus{ Ingress: []v1.LoadBalancerIngress{ - {"10.0.0.0", "fred"}, - {"10.0.0.1", "blee"}, + {IP: "10.0.0.0", Hostname: "fred"}, + {IP: "10.0.0.1", Hostname: "blee"}, }, } @@ -89,7 +89,7 @@ func k8sSVCLb() *v1.Service { ObjectMeta: metav1.ObjectMeta{ Name: "fred", Namespace: "blee", - CreationTimestamp: metav1.Time{testTime()}, + CreationTimestamp: metav1.Time{Time: testTime()}, }, Spec: v1.ServiceSpec{ Type: v1.ServiceTypeLoadBalancer, diff --git a/internal/resource/svc_test.go b/internal/resource/svc_test.go index bea77475..675a545f 100644 --- a/internal/resource/svc_test.go +++ b/internal/resource/svc_test.go @@ -113,7 +113,7 @@ func k8sSVC() *v1.Service { ObjectMeta: metav1.ObjectMeta{ Name: "fred", Namespace: "blee", - CreationTimestamp: metav1.Time{testTime()}, + CreationTimestamp: metav1.Time{Time: testTime()}, }, Spec: v1.ServiceSpec{ Type: v1.ServiceTypeClusterIP, diff --git a/internal/ui/action_test.go b/internal/ui/action_test.go index 01007753..34a7d669 100644 --- a/internal/ui/action_test.go +++ b/internal/ui/action_test.go @@ -18,5 +18,5 @@ func TestKeyActionsHints(t *testing.T) { hh := kk.Hints() assert.Equal(t, 3, len(hh)) - assert.Equal(t, model.MenuHint{"b", "blee", true}, hh[0]) + assert.Equal(t, model.MenuHint{Mnemonic: "b", Description: "blee", Visible: true}, hh[0]) } diff --git a/internal/ui/app_test.go b/internal/ui/app_test.go index c73e45ba..0aabd540 100644 --- a/internal/ui/app_test.go +++ b/internal/ui/app_test.go @@ -59,7 +59,9 @@ func TestAppViews(t *testing.T) { a := ui.NewApp() a.Init() - for _, v := range []string{"crumbs", "logo", "cmd", "flash", "menu"} { + vv := []string{"crumbs", "logo", "cmd", "flash", "menu"} + for i := range vv { + v := vv[i] t.Run(v, func(t *testing.T) { assert.NotNil(t, a.Views()[v]) }) diff --git a/internal/ui/cmd.go b/internal/ui/cmd.go index 9f0c92c2..ce745711 100644 --- a/internal/ui/cmd.go +++ b/internal/ui/cmd.go @@ -49,10 +49,6 @@ func (v *CmdView) update(s string) { v.write(s) } -func (v *CmdView) append(r rune) { - fmt.Fprintf(v, "%s", string(r)) -} - func (v *CmdView) write(s string) { fmt.Fprintf(v, defaultPrompt, v.icon, s) } diff --git a/internal/ui/cmd_buff.go b/internal/ui/cmd_buff.go index 6860faf6..daf653e9 100644 --- a/internal/ui/cmd_buff.go +++ b/internal/ui/cmd_buff.go @@ -25,11 +25,11 @@ type ( // CmdBuff represents user command input. CmdBuff struct { buff []rune + listeners []BuffWatcher + hotKey rune kind BufferKind sticky bool - hotKey rune active bool - listeners []BuffWatcher } ) @@ -90,10 +90,6 @@ func (c *CmdBuff) Delete() { c.fireChanged() } -func (c *CmdBuff) wipe() { - c.buff = make([]rune, 0, maxBuff) -} - // Clear clears out command buffer. func (c *CmdBuff) Clear() { c.buff = make([]rune, 0, maxBuff) diff --git a/internal/ui/colorer_test.go b/internal/ui/colorer_test.go index 54632980..77fb4fea 100644 --- a/internal/ui/colorer_test.go +++ b/internal/ui/colorer_test.go @@ -21,7 +21,8 @@ func TestDefaultColorer(t *testing.T) { "upd": {resource.RowEvent{Action: watch.Modified}, ui.ModColor}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, ui.DefaultColorer("", &u.re)) }) diff --git a/internal/ui/config.go b/internal/ui/config.go index 5992556e..0d98100c 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -43,7 +43,9 @@ func (c *Configurator) StylesUpdater(ctx context.Context, s synchronizer) error log.Info().Err(err).Msg("Skin watcher failed") return case <-ctx.Done(): - w.Close() + if err := w.Close(); err != nil { + log.Error().Err(err).Msg("Closing watcher") + } return } } diff --git a/internal/ui/deltas.go b/internal/ui/deltas.go index 8be6f137..a18e3c6f 100644 --- a/internal/ui/deltas.go +++ b/internal/ui/deltas.go @@ -21,6 +21,77 @@ const ( var percent = regexp.MustCompile(`\A(\d+)\%\z`) +func deltaNumb(o, n string) (string, bool) { + var delta string + + i, ok := numerical(o) + if !ok { + return delta, ok + } + + j, _ := numerical(n) + switch { + case i < j: + delta = PlusSign + case i > j: + delta = MinusSign + } + + return delta, ok +} + +func deltaPerc(o, n string) (string, bool) { + var delta string + i, ok := percentage(o) + if !ok { + return delta, ok + } + + j, _ := percentage(n) + switch { + case i < j: + delta = PlusSign + case i > j: + delta = MinusSign + } + + return delta, ok +} + +func deltaQty(o, n string) (string, bool) { + var delta string + q1, err := resource.ParseQuantity(o) + if err != nil { + return delta, false + } + + q2, _ := resource.ParseQuantity(n) + switch q1.Cmp(q2) { + case -1: + delta = PlusSign + case 1: + delta = MinusSign + } + return delta, true +} + +func deltaDur(o, n string) (string, bool) { + var delta string + d1, err := time.ParseDuration(o) + if err != nil { + return delta, false + } + + d2, _ := time.ParseDuration(n) + switch { + case d2-d1 > 0: + delta = PlusSign + case d2-d1 < 0: + delta = MinusSign + } + return delta, true +} + // Deltas signals diffs between 2 strings. func Deltas(o, n string) string { o, n = strings.TrimSpace(o), strings.TrimSpace(n) @@ -28,52 +99,20 @@ func Deltas(o, n string) string { return "" } - if i, ok := numerical(o); ok { - j, _ := numerical(n) - switch { - case i < j: - return PlusSign - case i > j: - return MinusSign - default: - return "" - } + if d, ok := deltaNumb(o, n); ok { + return d } - if i, ok := percentage(o); ok { - j, _ := percentage(n) - switch { - case i < j: - return PlusSign - case i > j: - return MinusSign - default: - return "" - } + if d, ok := deltaPerc(o, n); ok { + return d } - if q1, err := resource.ParseQuantity(o); err == nil { - q2, _ := resource.ParseQuantity(n) - switch q1.Cmp(q2) { - case -1: - return PlusSign - case 1: - return MinusSign - default: - return "" - } + if d, ok := deltaQty(o, n); ok { + return d } - if d1, err := time.ParseDuration(o); err == nil { - d2, _ := time.ParseDuration(n) - switch { - case d2-d1 > 0: - return PlusSign - case d2-d1 < 0: - return MinusSign - default: - return "" - } + if d, ok := deltaDur(o, n); ok { + return d } switch strings.Compare(o, n) { diff --git a/internal/ui/dialog/port_forward_test.go b/internal/ui/dialog/port_forward_test.go index 3c3a46c2..5c84c598 100644 --- a/internal/ui/dialog/port_forward_test.go +++ b/internal/ui/dialog/port_forward_test.go @@ -37,7 +37,8 @@ func TestStripPort(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, stripPort(u.port)) }) diff --git a/internal/ui/logo_test.go b/internal/ui/logo_test.go index fc163090..41c0f3a7 100644 --- a/internal/ui/logo_test.go +++ b/internal/ui/logo_test.go @@ -40,7 +40,8 @@ func TestLogoStatus(t *testing.T) { defaults, _ := config.NewStyles("") v := NewLogoView(defaults) - for k, u := range uu { + for n := range uu { + k, u := n, uu[n] t.Run(k, func(t *testing.T) { switch k { case "info": diff --git a/internal/ui/menu.go b/internal/ui/menu.go index 4d82d156..a83850c8 100644 --- a/internal/ui/menu.go +++ b/internal/ui/menu.go @@ -83,16 +83,13 @@ func (v *Menu) hasDigits(hh model.MenuHints) bool { } func (v *Menu) buildMenuTable(hh model.MenuHints) [][]string { - table := make([]model.MenuHints, maxRows+1) - + table := make([][]string, maxRows+1) colCount := (len(hh) / maxRows) + 1 - if v.hasDigits(hh) { colCount++ } - for row := 0; row < maxRows; row++ { - table[row] = make(model.MenuHints, colCount) + table[row] = make([]string, colCount) } var row, col int @@ -102,35 +99,24 @@ func (v *Menu) buildMenuTable(hh model.MenuHints) [][]string { if !h.Visible { continue } - isDigit := menuRX.MatchString(h.Mnemonic) - if !isDigit && firstCmd { + + if !menuRX.MatchString(h.Mnemonic) && firstCmd { row, col, firstCmd = 0, col+1, false - if table[0][0].IsBlank() { + if table[0][0] == "" { col = 0 } } if maxKeys[col] < len(h.Mnemonic) { maxKeys[col] = len(h.Mnemonic) } - table[row][col] = h + table[row][col] = keyConv(v.formatMenu(h, maxKeys[col])) row++ if row >= maxRows { - col++ - row = 0 + row, col = 0, col+1 } } - strTable := make([][]string, maxRows+1) - for r := 0; r < len(table); r++ { - strTable[r] = make([]string, len(table[r])) - } - for row := range strTable { - for col := range strTable[row] { - strTable[row][col] = keyConv(v.formatMenu(table[row][col], maxKeys[col])) - } - } - - return strTable + return table } // ---------------------------------------------------------------------------- diff --git a/internal/ui/menu_test.go b/internal/ui/menu_test.go index 9be0100b..5813ac03 100644 --- a/internal/ui/menu_test.go +++ b/internal/ui/menu_test.go @@ -45,7 +45,8 @@ func TestActionHints(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, u.aa.Hints()) }) diff --git a/internal/ui/sorter_test.go b/internal/ui/sorter_test.go index 9016ce8a..e0c64d65 100644 --- a/internal/ui/sorter_test.go +++ b/internal/ui/sorter_test.go @@ -114,7 +114,8 @@ func TestIsDurationSort(t *testing.T) { "ascGreater": {"10h10m", "2h5m", true, false}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { less, ok := isDurationSort(u.asc, u.s1, u.s2) assert.True(t, ok) diff --git a/internal/ui/table.go b/internal/ui/table.go index 77c27430..602bcbc6 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -59,8 +59,16 @@ func NewTable(title string) *Table { } } +func mustExtractSyles(ctx context.Context) *config.Styles { + styles, ok := ctx.Value(KeyStyles).(*config.Styles) + if !ok { + log.Fatal().Msg("Expecting valid styles") + } + return styles +} + func (t *Table) Init(ctx context.Context) { - t.styles = ctx.Value(KeyStyles).(*config.Styles) + t.styles = mustExtractSyles(ctx) t.SetFixed(1, 0) t.SetBorder(true) @@ -350,8 +358,8 @@ func (t *Table) buildRow(row int, data resource.TableData, sk string, pads MaxyP m := t.isMarked(sk) for col, field := range data.Rows[sk].Fields { header := data.Header[col] - field, align := t.formatCell(data.NumCols[header], header, field+Deltas(data.Rows[sk].Deltas[col], field), pads[col]) - c := tview.NewTableCell(field) + cell, align := t.formatCell(data.NumCols[header], header, field+Deltas(data.Rows[sk].Deltas[col], field), pads[col]) + c := tview.NewTableCell(cell) { c.SetExpansion(1) c.SetAlign(align) @@ -418,10 +426,10 @@ func (t *Table) filtered() resource.TableData { return t.fuzzyFilter(q[2:]) } - return t.rxFilter(q) + return t.rxFilter() } -func (t *Table) rxFilter(q string) resource.TableData { +func (t *Table) rxFilter() resource.TableData { rx, err := regexp.Compile(`(?i)` + t.cmdBuff.String()) if err != nil { log.Error().Err(errors.New("Invalid filter expression")).Msg("Regexp") diff --git a/internal/ui/table_helper_test.go b/internal/ui/table_helper_test.go index 1afe47bb..d3f3fb18 100644 --- a/internal/ui/table_helper_test.go +++ b/internal/ui/table_helper_test.go @@ -18,7 +18,8 @@ func TestIsLabelSelector(t *testing.T) { "wrongLabel": {"-f app=fred,env=blee", false}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, IsLabelSelector(u.sel)) }) @@ -33,7 +34,8 @@ func TestTrimLabelSelector(t *testing.T) { "noSpace": {"-lapp=fred,env=blee", "app=fred,env=blee"}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, TrimLabelSelector(u.sel)) }) diff --git a/internal/view/alias.go b/internal/view/alias.go index 18af43c6..e6f0cca8 100644 --- a/internal/view/alias.go +++ b/internal/view/alias.go @@ -64,10 +64,6 @@ func (a *Alias) registerActions() { }) } -func (a *Alias) getTitle() string { - return aliasTitle -} - func (a *Alias) resetCmd(evt *tcell.EventKey) *tcell.EventKey { if !a.SearchBuff().Empty() { a.SearchBuff().Reset() @@ -83,7 +79,9 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { s := ui.TrimCell(a.Table.Table, r, 1) tokens := strings.Split(s, ",") a.app.Content.Pop() - a.app.gotoResource(tokens[0], true) + if !a.app.gotoResource(tokens[0]) { + a.app.Flash().Err(fmt.Errorf("Goto %s failed", tokens[0])) + } return nil } @@ -94,7 +92,7 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } -func (a *Alias) backCmd(evt *tcell.EventKey) *tcell.EventKey { +func (a *Alias) backCmd(_ *tcell.EventKey) *tcell.EventKey { if a.SearchBuff().IsActive() { a.SearchBuff().Reset() } else { diff --git a/internal/view/alias_test.go b/internal/view/alias_test.go index e9cd01da..b794ddad 100644 --- a/internal/view/alias_test.go +++ b/internal/view/alias_test.go @@ -17,7 +17,7 @@ func TestAliasNew(t *testing.T) { v.Init(makeContext()) assert.Equal(t, 3, v.GetColumnCount()) - assert.Equal(t, 16, v.GetRowCount()) + assert.Equal(t, 15, v.GetRowCount()) assert.Equal(t, "Aliases", v.Name()) assert.Equal(t, 10, len(v.Hints())) } diff --git a/internal/view/app.go b/internal/view/app.go index 0b744f99..0c5c45c6 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -2,6 +2,7 @@ package view import ( "context" + "errors" "fmt" "time" @@ -152,7 +153,10 @@ func (a *App) BufferActive(state bool, _ ui.BufferKind) { func (a *App) toggleHeader(flag bool) { a.showHeader = flag - flex := a.Main.GetPrimitive("main").(*tview.Flex) + flex, ok := a.Main.GetPrimitive("main").(*tview.Flex) + if !ok { + log.Fatal().Msg("Expecting valid flex view") + } if a.showHeader { flex.RemoveItemAtIndex(0) flex.AddItemAtIndex(0, a.buildHeader(), 7, 1, false) @@ -262,8 +266,8 @@ func (a *App) switchCtx(ctx string, load bool) error { log.Error().Err(err).Msg("Config save failed!") } a.Flash().Infof("Switching context to %s", ctx) - if load { - a.gotoResource("po", true) + if load && !a.gotoResource("po") { + a.Flash().Err(errors.New("Goto pod failed")) } return nil @@ -396,7 +400,9 @@ func (a *App) toggleHeaderCmd(evt *tcell.EventKey) *tcell.EventKey { func (a *App) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { if a.CmdBuff().IsActive() && !a.CmdBuff().Empty() { a.Content.Stack.Reset() - a.gotoResource(a.GetCmd(), true) + if !a.gotoResource(a.GetCmd()) { + a.Flash().Errf("Goto %s failed!", a.GetCmd()) + } a.ResetCmd() return nil } @@ -423,7 +429,7 @@ func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -func (a *App) gotoResource(res string, record bool) bool { +func (a *App) gotoResource(res string) bool { return a.command.run(res) } diff --git a/internal/view/bench.go b/internal/view/bench.go index 2903826e..203b7abb 100644 --- a/internal/view/bench.go +++ b/internal/view/bench.go @@ -106,10 +106,6 @@ func (b *Bench) keyBindings() { b.masterPage().AddActions(aa) } -func (b *Bench) getTitle() string { - return benchTitle -} - func (b *Bench) enterCmd(evt *tcell.EventKey) *tcell.EventKey { if b.masterPage().SearchBuff().IsActive() { return b.masterPage().filterCmd(evt) @@ -139,7 +135,7 @@ func (b *Bench) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { sel, file := b.masterPage().GetSelectedItem(), b.benchFile() dir := filepath.Join(perf.K9sBenchDir, b.app.Config.K9s.CurrentCluster) - showModal(b.Pages, fmt.Sprintf("Delete benchmark `%s?", file), "master", func() { + showModal(b.Pages, fmt.Sprintf("Delete benchmark `%s?", file), func() { if err := os.Remove(filepath.Join(dir, file)); err != nil { b.app.Flash().Errf("Unable to delete file %s", err) return @@ -150,11 +146,6 @@ func (b *Bench) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -func (b *Bench) backCmd(evt *tcell.EventKey) *tcell.EventKey { - b.showMaster() - return nil -} - func (b *Bench) benchFile() string { r := b.masterPage().GetSelectedRowIndex() return ui.TrimCell(b.masterPage().Table, r, 7) @@ -221,7 +212,9 @@ func (b *Bench) watchBenchDir(ctx context.Context) error { return case <-ctx.Done(): log.Debug().Msg("!!!! FS WATCHER DONE!!") - w.Close() + if err := w.Close(); err != nil { + log.Error().Err(err).Msg("Closing bench watched") + } return } } @@ -273,29 +266,25 @@ func augmentRow(fields resource.Row, data string) { col++ ms := okRx.FindAllStringSubmatch(data, -1) - fields[col] = "0" - if len(ms) > 0 { - var sum int - for _, m := range ms { - if m, err := strconv.Atoi(string(m[1])); err == nil { - sum += m - } - } - fields[col] = asNum(sum) - } + fields[col] = countReq(ms) col++ me := errRx.FindAllStringSubmatch(data, -1) - fields[col] = "0" - if len(me) > 0 { - var sum int - for _, m := range me { - if m, err := strconv.Atoi(string(m[1])); err == nil { - sum += m - } - } - fields[col] = asNum(sum) + fields[col] = countReq(me) +} + +func countReq(rr [][]string) string { + if len(rr) == 0 { + return "0" } + + var sum int + for _, m := range rr { + if m, err := strconv.Atoi(string(m[1])); err == nil { + sum += m + } + } + return asNum(sum) } func benchDir(cfg *config.Config) string { diff --git a/internal/view/bench_int_test.go b/internal/view/bench_int_test.go index 0a3b19a1..ef91b17d 100644 --- a/internal/view/bench_int_test.go +++ b/internal/view/bench_int_test.go @@ -31,7 +31,8 @@ func TestAugmentRow(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { data, err := ioutil.ReadFile(u.file) diff --git a/internal/view/cluster_info.go b/internal/view/cluster_info.go index 3c4768f7..1bdbceab 100644 --- a/internal/view/cluster_info.go +++ b/internal/view/cluster_info.go @@ -43,7 +43,7 @@ func newClusterInfoView(app *App, mx resource.MetricsServer) *clusterInfoView { func (v *clusterInfoView) init(version string) { cluster := resource.NewCluster(v.app.Conn(), &log.Logger, v.mxs) - row := v.initInfo(version, cluster) + row := v.initInfo(cluster) row = v.initVersion(row, version, cluster) v.SetCell(row, 0, v.sectionCell("CPU")) @@ -55,7 +55,7 @@ func (v *clusterInfoView) init(version string) { v.refresh() } -func (v *clusterInfoView) initInfo(version string, cluster *resource.Cluster) int { +func (v *clusterInfoView) initInfo(cluster *resource.Cluster) int { var row int v.SetCell(row, 0, v.sectionCell("Context")) v.SetCell(row, 1, v.infoCell(cluster.ContextName())) diff --git a/internal/view/colorer.go b/internal/view/colorer.go index ba59393b..61ede248 100644 --- a/internal/view/colorer.go +++ b/internal/view/colorer.go @@ -36,6 +36,18 @@ func rbacColorer(ns string, r *resource.RowEvent) tcell.Color { return ui.DefaultColorer(ns, r) } +func checkReadyCol(readyCol, statusCol string, c tcell.Color) tcell.Color { + if statusCol == "Completed" { + return c + } + + tokens := strings.Split(readyCol, "/") + if len(tokens) == 2 && (tokens[0] == "0" || tokens[0] != tokens[1]) { + return ui.ErrColor + } + return c +} + func podColorer(ns string, r *resource.RowEvent) tcell.Color { c := ui.DefaultColorer(ns, r) @@ -45,25 +57,21 @@ func podColorer(ns string, r *resource.RowEvent) tcell.Color { } statusCol := readyCol + 1 - tokens := strings.Split(strings.TrimSpace(r.Fields[readyCol]), "/") - if len(tokens) == 2 && (tokens[0] == "0" || tokens[0] != tokens[1]) { - if strings.TrimSpace(r.Fields[statusCol]) != "Completed" { - c = ui.ErrColor - } - } + ready, status := strings.TrimSpace(r.Fields[readyCol]), strings.TrimSpace(r.Fields[statusCol]) + c = checkReadyCol(ready, status, c) - switch strings.TrimSpace(r.Fields[statusCol]) { + switch status { case "ContainerCreating", "PodInitializing": return ui.AddColor - case "Initialized": + case resource.Initialized: return ui.HighlightColor - case "Completed": + case resource.Completed: return ui.CompletedColor - case "Running": - case "Terminating": + case resource.Running: + case resource.Terminating: return ui.KillColor default: - c = ui.ErrColor + return ui.ErrColor } return c @@ -81,11 +89,11 @@ func containerColorer(ns string, r *resource.RowEvent) tcell.Color { switch strings.TrimSpace(r.Fields[stateCol]) { case "ContainerCreating", "PodInitializing": return ui.AddColor - case "Terminating", "Initialized": + case resource.Terminating, resource.Initialized: return ui.HighlightColor - case "Completed": + case resource.Completed: return ui.CompletedColor - case "Running": + case resource.Running: default: c = ui.ErrColor } @@ -236,7 +244,7 @@ func nsColorer(ns string, r *resource.RowEvent) tcell.Color { } switch strings.TrimSpace(r.Fields[1]) { - case "Inactive", "Terminating": + case "Inactive", resource.Terminating: c = ui.ErrColor } diff --git a/internal/view/colorer_test.go b/internal/view/colorer_test.go index a178d24c..6aebe059 100644 --- a/internal/view/colorer_test.go +++ b/internal/view/colorer_test.go @@ -22,7 +22,7 @@ type ( func TestNSColorer(t *testing.T) { var ( ns = resource.Row{"blee", "Active"} - term = resource.Row{"blee", "Terminating"} + term = resource.Row{"blee", resource.Terminating} dead = resource.Row{"blee", "Inactive"} ) diff --git a/internal/view/command.go b/internal/view/command.go index 001b5544..b33f1521 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -99,15 +99,12 @@ func (c *command) run(cmd string) bool { } switch cmds[0] { case "ctx", "context", "contexts": - if len(cmds) == 2 { - if err := c.app.switchCtx(cmds[1], true); err != nil { - log.Error().Err(err).Msg("Context switch failed!") - return false - } - return true + if len(cmds) == 2 && c.app.switchCtx(cmds[1], true) != nil { + log.Error().Msg("Context switch failed!") + return false } view := c.componentFor(gvr, v) - return c.exec(gvr, "", view) + return c.exec(gvr, view) default: ns := c.app.Config.ActiveNamespace() if len(cmds) == 2 { @@ -116,7 +113,7 @@ func (c *command) run(cmd string) bool { if !c.app.switchNS(ns) { return false } - return c.exec(gvr, ns, c.componentFor(gvr, v)) + return c.exec(gvr, c.componentFor(gvr, v)) } } @@ -145,7 +142,7 @@ func (c *command) componentFor(gvr string, v *viewer) ResourceViewer { return view } -func (c *command) exec(gvr string, ns string, comp model.Component) bool { +func (c *command) exec(gvr string, comp model.Component) bool { if comp == nil { log.Error().Err(fmt.Errorf("No component given for %s", gvr)) return false diff --git a/internal/view/container.go b/internal/view/container.go index e2344e2f..682d23cb 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -56,10 +56,10 @@ func (c *Container) extraActions(aa ui.KeyActions) { aa[ui.KeyShiftF] = ui.NewKeyAction("PortForward", c.portFwdCmd, true) aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", c.prevLogsCmd, true) aa[ui.KeyS] = ui.NewKeyAction("Shell", c.shellCmd, true) - aa[ui.KeyShiftC] = ui.NewKeyAction("Sort CPU", c.sortColCmd(6, false), false) - aa[ui.KeyShiftM] = ui.NewKeyAction("Sort MEM", c.sortColCmd(7, false), false) - aa[ui.KeyShiftX] = ui.NewKeyAction("Sort CPU%", c.sortColCmd(8, false), false) - aa[ui.KeyShiftZ] = ui.NewKeyAction("Sort MEM%", c.sortColCmd(9, false), false) + aa[ui.KeyShiftC] = ui.NewKeyAction("Sort CPU", c.sortColCmd(6), false) + aa[ui.KeyShiftM] = ui.NewKeyAction("Sort MEM", c.sortColCmd(7), false) + aa[ui.KeyShiftX] = ui.NewKeyAction("Sort CPU%", c.sortColCmd(8), false) + aa[ui.KeyShiftZ] = ui.NewKeyAction("Sort MEM%", c.sortColCmd(9), false) } func (c *Container) k9sEnv() K9sEnv { diff --git a/internal/view/context.go b/internal/view/context.go index 606f1b48..bac1d6b9 100644 --- a/internal/view/context.go +++ b/internal/view/context.go @@ -2,6 +2,7 @@ package view import ( "context" + "errors" "strings" "github.com/derailed/k9s/internal/resource" @@ -37,7 +38,9 @@ func (c *Context) useCtx(app *App, _, res, sel string) { app.Flash().Err(err) return } - app.gotoResource("po", true) + if !app.gotoResource("po") { + app.Flash().Err(errors.New("Goto pod failed")) + } } func (*Context) cleanser(s string) string { diff --git a/internal/view/context_int_test.go b/internal/view/context_int_test.go index ae7160e9..d56150ba 100644 --- a/internal/view/context_int_test.go +++ b/internal/view/context_int_test.go @@ -16,7 +16,8 @@ func TestCleaner(t *testing.T) { } v := Context{} - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, v.cleanser(u.s)) }) diff --git a/internal/view/details.go b/internal/view/details.go index b367dcb3..4fdd0754 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -42,7 +42,7 @@ func NewDetails(app *App, backFn ui.ActionHandler) *Details { // Init initializes the viewer. func (d *Details) Init(ctx context.Context) { - d.app = ctx.Value(ui.KeyApp).(*App) + d.app = mustExtractApp(ctx) d.SetScrollable(true) d.SetWrap(true) @@ -128,7 +128,7 @@ func (d *Details) cpCmd(evt *tcell.EventKey) *tcell.EventKey { func (d *Details) backCmd(evt *tcell.EventKey) *tcell.EventKey { if !d.cmdBuff.Empty() { d.cmdBuff.Reset() - d.search(evt) + d.search() return nil } d.cmdBuff.Reset() @@ -146,31 +146,7 @@ func (d *Details) eraseCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -func (d *Details) activateCmd(evt *tcell.EventKey) *tcell.EventKey { - if !d.app.InCmdMode() { - d.cmdBuff.SetActive(true) - d.cmdBuff.Clear() - return nil - } - return evt -} - -func (d *Details) searchCmd(evt *tcell.EventKey) *tcell.EventKey { - if d.cmdBuff.IsActive() && !d.cmdBuff.Empty() { - d.app.Flash().Infof("Searching for %s...", d.cmdBuff) - d.search(evt) - highlights := d.GetHighlights() - if len(highlights) > 0 { - d.Highlight() - } else { - d.Highlight("0").ScrollToHighlight() - } - } - d.cmdBuff.SetActive(false) - return evt -} - -func (d *Details) search(evt *tcell.EventKey) { +func (d *Details) search() { d.numSelections = 0 log.Debug().Msgf("Searching... %s - %d", d.cmdBuff, d.numSelections) d.Highlight("") @@ -216,13 +192,6 @@ func (d *Details) prevCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -// SetActions to handle keyboard inputs -func (d *Details) setActions(aa ui.KeyActions) { - for k, a := range aa { - d.actions[k] = a - } -} - // Hints fetch mmemonic and hints func (d *Details) Hints() model.MenuHints { return d.actions.Hints() diff --git a/internal/view/dp.go b/internal/view/dp.go index 3c3b30bd..f3335bc8 100644 --- a/internal/view/dp.go +++ b/internal/view/dp.go @@ -4,6 +4,7 @@ import ( "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" + "github.com/rs/zerolog/log" v1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -36,8 +37,8 @@ func (d *Deploy) extraActions(aa ui.KeyActions) { d.LogResource.extraActions(aa) d.scalableResource.extraActions(aa) d.restartableResource.extraActions(aa) - aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", d.sortColCmd(1, false), false) - aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", d.sortColCmd(2, false), false) + aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", d.sortColCmd(1), false) + aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", d.sortColCmd(2), false) } func (d *Deploy) showPods(app *App, _, res, sel string) { @@ -48,12 +49,15 @@ func (d *Deploy) showPods(app *App, _, res, sel string) { return } - dp := dep.(*v1.Deployment) + dp, ok := dep.(*v1.Deployment) + if !ok { + log.Fatal().Msg("Expecting valid deployment") + } l, err := metav1.LabelSelectorAsSelector(dp.Spec.Selector) if err != nil { app.Flash().Err(err) return } - showPods(app, ns, l.String(), "", d.backCmd) + showPods(app, ns, l.String(), "") } diff --git a/internal/view/ds.go b/internal/view/ds.go index c5d8e591..3a1254bc 100644 --- a/internal/view/ds.go +++ b/internal/view/ds.go @@ -4,6 +4,7 @@ import ( "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" + "github.com/rs/zerolog/log" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -29,8 +30,8 @@ func NewDaemonSet(title, gvr string, list resource.List) ResourceViewer { func (d *DaemonSet) extraActions(aa ui.KeyActions) { d.LogResource.extraActions(aa) d.restartableResource.extraActions(aa) - aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", d.sortColCmd(1, false), false) - aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", d.sortColCmd(2, false), false) + aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", d.sortColCmd(1), false) + aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", d.sortColCmd(2), false) } func (d *DaemonSet) showPods(app *App, _, res, sel string) { @@ -41,12 +42,15 @@ func (d *DaemonSet) showPods(app *App, _, res, sel string) { return } - ds := dset.(*appsv1.DaemonSet) + ds, ok := dset.(*appsv1.DaemonSet) + if !ok { + log.Fatal().Msg("Expecting a valid ds") + } l, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector) if err != nil { app.Flash().Err(err) return } - showPods(app, ns, l.String(), "", d.backCmd) + showPods(app, ns, l.String(), "") } diff --git a/internal/view/env_test.go b/internal/view/env_test.go index 6b31a387..cff67da4 100644 --- a/internal/view/env_test.go +++ b/internal/view/env_test.go @@ -27,7 +27,8 @@ func TestK9sEnv(t *testing.T) { "COL0": "fred", } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { a, err := e.envFor(u.q) assert.Equal(t, u.err, err) diff --git a/internal/view/help.go b/internal/view/help.go index 2673f1e2..50ed4f6a 100644 --- a/internal/view/help.go +++ b/internal/view/help.go @@ -37,7 +37,7 @@ func NewHelp() *Help { } func (v *Help) Init(ctx context.Context) { - v.app = ctx.Value(ui.KeyApp).(*App) + v.app = mustExtractApp(ctx) v.resetTitle() @@ -171,10 +171,6 @@ func (v *Help) showGeneral() model.MenuHints { } } -func (v *Help) getTitle() string { - return helpTitle -} - func (v *Help) resetTitle() { v.SetTitle(fmt.Sprintf(helpTitleFmt, helpTitle)) } @@ -182,20 +178,20 @@ func (v *Help) resetTitle() { func (v *Help) build(hh model.MenuHints) { v.Clear() sort.Sort(hh) - v.addSection(0, 0, "RESOURCE", hh) - v.addSection(0, 4, "GENERAL", v.showGeneral()) - v.addSection(0, 6, "NAVIGATION", v.showNav()) - v.addSection(0, 8, "HELP", v.showHelp()) + v.addSection(0, "RESOURCE", hh) + v.addSection(4, "GENERAL", v.showGeneral()) + v.addSection(6, "NAVIGATION", v.showNav()) + v.addSection(8, "HELP", v.showHelp()) } -func (v *Help) addSection(r, c int, title string, hh model.MenuHints) { - row := r +func (v *Help) addSection(c int, title string, hh model.MenuHints) { + row := 0 cell := tview.NewTableCell(title) cell.SetTextColor(tcell.ColorGreen) cell.SetAttributes(tcell.AttrBold) cell.SetExpansion(2) cell.SetAlign(tview.AlignLeft) - v.SetCell(r, c+1, cell) + v.SetCell(row, c+1, cell) row++ for _, h := range hh { diff --git a/internal/view/help_test.go b/internal/view/help_test.go index 1cdb3159..edb22cfc 100644 --- a/internal/view/help_test.go +++ b/internal/view/help_test.go @@ -7,16 +7,8 @@ import ( "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/view" "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func newNS(n string) v1.Namespace { - return v1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: n, - }} -} - func TestHelpNew(t *testing.T) { ctx := makeCtx() diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 6243db53..036a3b06 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -57,7 +57,7 @@ func containerID(path, co string) string { } // UrlFor computes fq url for a given benchmark configuration. -func urlFor(cfg config.BenchConfig, co, port string) string { +func urlFor(cfg config.BenchConfig, port string) string { host := "localhost" if cfg.HTTP.Host != "" { host = cfg.HTTP.Host diff --git a/internal/view/helpers_test.go b/internal/view/helpers_test.go index 2018dc6a..3b414f83 100644 --- a/internal/view/helpers_test.go +++ b/internal/view/helpers_test.go @@ -21,7 +21,8 @@ func TestIsTCPPort(t *testing.T) { "udp": {"80╱UDP", false}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, isTCPPort(u.p)) }) @@ -36,7 +37,8 @@ func TestFQN(t *testing.T) { "allNS": {"", "fred", "fred"}, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, fqn(u.ns, u.n)) }) @@ -75,9 +77,10 @@ func TestUrlFor(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, urlFor(u.cfg, u.co, u.port)) + assert.Equal(t, u.e, urlFor(u.cfg, u.port)) }) } } @@ -98,7 +101,8 @@ func TestContainerID(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, containerID(u.path, u.co)) }) diff --git a/internal/view/job.go b/internal/view/job.go index 50f989d3..c7bf34a0 100644 --- a/internal/view/job.go +++ b/internal/view/job.go @@ -4,6 +4,7 @@ import ( "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" + "github.com/rs/zerolog/log" batchv1 "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -34,12 +35,15 @@ func (j *Job) showPods(app *App, _, res, sel string) { return } - jo := job.(*batchv1.Job) + jo, ok := job.(*batchv1.Job) + if !ok { + log.Fatal().Msg("Expecting a valid job") + } l, err := metav1.LabelSelectorAsSelector(jo.Spec.Selector) if err != nil { app.Flash().Err(err) return } - showPods(app, ns, l.String(), "", j.backCmd) + showPods(app, ns, l.String(), "") } diff --git a/internal/view/log.go b/internal/view/log.go index 13a0606a..76689763 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -162,17 +162,16 @@ func saveData(cluster, name, data string) (string, error) { path := filepath.Join(dir, fName) mod := os.O_CREATE | os.O_WRONLY - file, err := os.OpenFile(path, mod, 0644) - defer func() { - if file != nil { - file.Close() - } - }() + file, err := os.OpenFile(path, mod, 0600) if err != nil { log.Error().Err(err).Msgf("LogFile create %s", path) return "", nil } - + defer func() { + if err := file.Close(); err != nil { + log.Error().Err(err).Msg("Closing Log file") + } + }() if _, err := file.Write([]byte(data)); err != nil { return "", err } diff --git a/internal/view/log_resource.go b/internal/view/log_resource.go index 43c5fb4b..97a0428f 100644 --- a/internal/view/log_resource.go +++ b/internal/view/log_resource.go @@ -6,7 +6,6 @@ import ( "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" - "github.com/rs/zerolog/log" ) // ContainerFn returns the active container name. @@ -39,10 +38,10 @@ func (l *LogResource) extraActions(aa ui.KeyActions) { aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", l.prevLogsCmd, true) } -func (l *LogResource) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { +func (l *LogResource) sortColCmd(col int) func(evt *tcell.EventKey) *tcell.EventKey { return func(evt *tcell.EventKey) *tcell.EventKey { t := l.masterPage() - t.SetSortCol(t.NameColIndex()+col, 0, asc) + t.SetSortCol(t.NameColIndex()+col, 0, false) t.Refresh() return nil @@ -84,12 +83,3 @@ func (l *LogResource) showLogs(prev bool) { l.logs.reload(co, l, prev) l.Push(l.logs) } - -func (l *LogResource) backCmd(evt *tcell.EventKey) *tcell.EventKey { - if err := l.app.Config.SetActiveNamespace(l.list.GetNamespace()); err != nil { - log.Error().Err(err).Msg("Config NS set failed!") - } - l.app.inject(l) - - return nil -} diff --git a/internal/view/log_test.go b/internal/view/log_test.go index d8719630..82ca51a6 100644 --- a/internal/view/log_test.go +++ b/internal/view/log_test.go @@ -23,7 +23,7 @@ func TestAnsi(t *testing.T) { v.SetDynamicColors(true) aw := tview.ANSIWriter(v, "white", "black") s := "[2019-03-27T15:05:15,246][INFO ][o.e.c.r.a.AllocationService] [es-0] Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.monitoring-es-6-2019.03.27][0]]" - fmt.Fprintf(aw, s) + fmt.Fprintf(aw, "%s", s) assert.Equal(t, s+"\n", v.GetText(false)) } diff --git a/internal/view/logs.go b/internal/view/logs.go index 5b4ecc66..6213c987 100644 --- a/internal/view/logs.go +++ b/internal/view/logs.go @@ -26,7 +26,6 @@ type Logs struct { app *App parent Loggable - actions ui.KeyActions cancelFunc context.CancelFunc } @@ -39,7 +38,7 @@ func NewLogs(title string, parent Loggable) *Logs { } func (l *Logs) Init(ctx context.Context) { - l.app = ctx.Value(ui.KeyApp).(*App) + l.app = mustExtractApp(ctx) } func (l *Logs) Start() {} @@ -55,21 +54,21 @@ func (l *Logs) reload(co string, parent Loggable, prevLogs bool) { l.load(co, prevLogs) } -// SetActions to handle keyboard events. -func (l *Logs) setActions(aa ui.KeyActions) { - l.actions = aa +func (l *Logs) mustLogViewer() *Log { + v, ok := l.CurrentPage().Item.(*Log) + if !ok { + log.Fatal().Msg("Expecting a log viewer") + } + + return v } // Hints show action hints func (l *Logs) Hints() model.MenuHints { - v := l.CurrentPage().Item.(*Log) + v := l.mustLogViewer() return v.actions.Hints() } -func (l *Logs) backFn() ui.ActionHandler { - return l.backCmd -} - func (l *Logs) deletePage() { l.RemovePage("logs") } @@ -86,8 +85,8 @@ func (l *Logs) stop() { func (l *Logs) load(container string, prevLogs bool) { if err := l.doLoad(l.parent.getSelection(), container, prevLogs); err != nil { l.app.Flash().Err(err) - l := l.CurrentPage().Item.(*Log) - l.log("😂 Doh! No logs are available at this time. Check again later on...") + v := l.mustLogViewer() + v.log("😂 Doh! No logs are available at this time. Check again later on...") return } l.app.SetFocus(l) @@ -96,7 +95,7 @@ func (l *Logs) load(container string, prevLogs bool) { func (l *Logs) doLoad(path, co string, prevLogs bool) error { l.stop() - v := l.CurrentPage().Item.(*Log) + v := l.mustLogViewer() v.logs.Clear() v.setTitle(path, co) diff --git a/internal/view/master_detail.go b/internal/view/master_detail.go index b52160df..b69f4a34 100644 --- a/internal/view/master_detail.go +++ b/internal/view/master_detail.go @@ -30,9 +30,18 @@ func NewMasterDetail(title, ns string) *MasterDetail { } } +func mustExtractApp(ctx context.Context) *App { + app, ok := ctx.Value(ui.KeyApp).(*App) + if !ok { + panic("No application given in context") + } + + return app +} + // Init initializes the viewer. func (m *MasterDetail) Init(ctx context.Context) { - app := ctx.Value(ui.KeyApp).(*App) + app := mustExtractApp(ctx) if m.currentNS != resource.NotNamespaced { m.currentNS = app.Config.ActiveNamespace() } @@ -68,10 +77,6 @@ func (m *MasterDetail) setEnterFn(f enterFn) { m.enterFn = f } -func (m *MasterDetail) showMaster() { - m.Show(m.master) -} - func (m *MasterDetail) masterPage() *Table { return m.master } diff --git a/internal/view/no.go b/internal/view/no.go index d2e5c0c6..e5796779 100644 --- a/internal/view/no.go +++ b/internal/view/no.go @@ -3,7 +3,6 @@ package view import ( "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" - "github.com/gdamore/tcell" "github.com/rs/zerolog/log" ) @@ -31,14 +30,10 @@ func (n *Node) extraActions(aa ui.KeyActions) { } func (n *Node) showPods(app *App, _, _, sel string) { - showPods(app, "", "", "spec.nodeName="+sel, n.backCmd) + showPods(app, "", "", "spec.nodeName="+sel) } -func (n *Node) backCmd(evt *tcell.EventKey) *tcell.EventKey { - return nil -} - -func showPods(app *App, ns, labelSel, fieldSel string, a ui.ActionHandler) { +func showPods(app *App, ns, labelSel, fieldSel string) { app.switchNS(ns) list := resource.NewPodList(app.Conn(), ns) diff --git a/internal/view/ns.go b/internal/view/ns.go index def9e024..1f05fd49 100644 --- a/internal/view/ns.go +++ b/internal/view/ns.go @@ -44,7 +44,7 @@ func (n *Namespace) extraActions(aa ui.KeyActions) { func (n *Namespace) switchNs(app *App, _, res, sel string) { n.useNamespace(sel) - app.gotoResource("po", true) + app.gotoResource("po") } func (n *Namespace) useNsCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/page_stack.go b/internal/view/page_stack.go index 6f6e6179..bb2972e8 100644 --- a/internal/view/page_stack.go +++ b/internal/view/page_stack.go @@ -20,7 +20,7 @@ func NewPageStack() *PageStack { } func (p *PageStack) Init(ctx context.Context) { - p.app = ctx.Value(ui.KeyApp).(*App) + p.app = mustExtractApp(ctx) p.Stack.AddListener(p) } diff --git a/internal/view/pod.go b/internal/view/pod.go index 0cf9141d..bb07b5ee 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -2,6 +2,7 @@ package view import ( "context" + "errors" "fmt" "github.com/derailed/k9s/internal/model" @@ -81,7 +82,10 @@ func (p *Pod) listContainers(app *App, _, res, sel string) { return } - pod := po.(*v1.Pod) + pod, ok := po.(*v1.Pod) + if !ok { + log.Fatal().Msg("Expecting a valid pod") + } list := resource.NewContainerList(app.Conn(), pod) title := skinTitle(fmt.Sprintf(containerFmt, "Container", sel), app.Styles.Frame()) @@ -144,12 +148,12 @@ func (p *Pod) viewLogs(prev bool) bool { if !p.masterPage().RowSelected() { return false } - p.showLogs(p.masterPage().GetSelectedItem(), "", p, prev) + p.showLogs("", p, prev) return true } -func (p *Pod) showLogs(path, co string, parent Loggable, prev bool) { +func (p *Pod) showLogs(co string, parent Loggable, prev bool) { p.logs.reload(co, parent, prev) p.Push(p.logs) } @@ -169,7 +173,10 @@ func (p *Pod) shellCmd(evt *tcell.EventKey) *tcell.EventKey { p.shellIn(sel, "") return nil } - picker := p.GetPrimitive("picker").(*selectList) + picker, ok := p.GetPrimitive("picker").(*selectList) + if !ok { + log.Fatal().Msg("Expecting a valid selectlist") + } picker.populate(cc) picker.SetSelectedFunc(func(i int, t, d string, r rune) { p.shellIn(sel, t) @@ -198,7 +205,9 @@ func fetchContainers(l resource.List, po string, includeInit bool) ([]string, er func shellIn(a *App, path, co string) { args := computeShellArgs(path, co, a.Config.K9s.CurrentContext, a.Conn().Config().Flags().KubeConfig) log.Debug().Msgf("Shell args %v", args) - runK(true, a, args...) + if !runK(true, a, args...) { + a.Flash().Err(errors.New("Shell exec failed")) + } } func computeShellArgs(path, co, context string, kcfg *string) []string { diff --git a/internal/view/pod_int_test.go b/internal/view/pod_int_test.go index 2f745f3c..2351bade 100644 --- a/internal/view/pod_int_test.go +++ b/internal/view/pod_int_test.go @@ -44,7 +44,8 @@ func TestComputeShellArgs(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { args := computeShellArgs(u.path, u.co, u.context, u.cfg) diff --git a/internal/view/policy.go b/internal/view/policy.go index 95dcefe1..ccba6ab0 100644 --- a/internal/view/policy.go +++ b/internal/view/policy.go @@ -13,7 +13,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const policyTitle = "Policy" +const ( + policyTitle = "Policy" + group = "Group" + user = "User" + sa = "ServiceAccount" +) var policyHeader = append(resource.Row{"NAMESPACE", "NAME", "API GROUP", "BINDING"}, rbacHeaderVerbs...) @@ -294,10 +299,10 @@ func policyRow(ns, res, grp, binding string) resource.Row { func mapSubject(subject string) string { switch subject { case "g": - return "Group" + return group case "s": - return "ServiceAccount" + return sa default: - return "User" + return user } } diff --git a/internal/view/port_forward.go b/internal/view/port_forward.go index 6042f019..313dda9f 100644 --- a/internal/view/port_forward.go +++ b/internal/view/port_forward.go @@ -8,7 +8,6 @@ import ( "time" "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/perf" "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" @@ -19,9 +18,8 @@ import ( ) const ( - portForwardTitle = "PortForwards" - portForwardTitleFmt = " [aqua::b]%s([fuchsia::b]%d[fuchsia::-])[aqua::-] " - promptPage = "prompt" + portForwardTitle = "PortForwards" + promptPage = "prompt" ) // PortForward presents active portforward viewer. @@ -42,7 +40,7 @@ func NewPortForward(title, gvr string, list resource.List) ResourceViewer { // Init the view. func (p *PortForward) Init(ctx context.Context) { - p.app = ctx.Value(ui.KeyApp).(*App) + p.app = mustExtractApp(ctx) p.MasterDetail.Init(ctx) p.registerActions() @@ -108,12 +106,8 @@ func (p *PortForward) registerActions() { }) } -func (p *PortForward) getTitle() string { - return portForwardTitle -} - func (p *PortForward) gotoBenchCmd(evt *tcell.EventKey) *tcell.EventKey { - p.app.gotoResource("be", true) + p.app.gotoResource("be") return nil } @@ -206,7 +200,7 @@ func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - showModal(p.Pages, fmt.Sprintf("Delete PortForward `%s?", sel), "table", func() { + showModal(p.Pages, fmt.Sprintf("Delete PortForward `%s?", sel), func() { fw, ok := p.app.forwarders[sel] if !ok { log.Debug().Msgf("Unable to find forwarder %s", sel) @@ -223,21 +217,6 @@ func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -func (p *PortForward) backCmd(evt *tcell.EventKey) *tcell.EventKey { - if p.cancelFn != nil { - p.cancelFn() - } - - tv := p.masterPage() - if tv.SearchBuff().IsActive() { - tv.SearchBuff().Reset() - } else { - p.app.inject(p.app.Content.GetPrimitive("main").(model.Component)) - } - - return nil -} - func (p *PortForward) hydrate() resource.TableData { data := initHeader(len(p.app.forwarders)) dc, dn := p.app.Bench.Benchmarks.Defaults.C, p.app.Bench.Benchmarks.Defaults.N @@ -251,7 +230,7 @@ func (p *PortForward) hydrate() resource.TableData { na, f.Container(), strings.Join(f.Ports(), ","), - urlFor(cfg, f.Container(), ports[0]), + urlFor(cfg, ports[0]), asNum(c), asNum(n), f.Age(), @@ -266,10 +245,6 @@ func (p *PortForward) hydrate() resource.TableData { return data } -func (p *PortForward) resetTitle() { - p.SetTitle(fmt.Sprintf(portForwardTitleFmt, portForwardTitle, p.masterPage().GetRowCount()-1)) -} - // ---------------------------------------------------------------------------- // Helpers... @@ -310,7 +285,7 @@ func loadConfig(dc, dn int, id string, cc map[string]config.BenchConfig) (int, i return c, n, cfg } -func showModal(p *ui.Pages, msg, back string, ok func()) { +func showModal(p *ui.Pages, msg string, ok func()) { m := tview.NewModal(). AddButtons([]string{"Cancel", "OK"}). SetTextColor(tcell.ColorFuchsia). @@ -319,14 +294,14 @@ func showModal(p *ui.Pages, msg, back string, ok func()) { if b == "OK" { ok() } - dismissModal(p, back) + dismissModal(p) }) m.SetTitle("") p.AddPage(promptPage, m, false, false) p.ShowPage(promptPage) } -func dismissModal(p *ui.Pages, page string) { +func dismissModal(p *ui.Pages) { p.RemovePage(promptPage) } @@ -352,7 +327,9 @@ func watchFS(ctx context.Context, app *App, dir, file string, cb func()) error { return case <-ctx.Done(): log.Debug().Msgf("<>", dir) - w.Close() + if err := w.Close(); err != nil { + log.Error().Err(err).Msg("Closing portforward watcher") + } return } } diff --git a/internal/view/port_selector.go b/internal/view/port_selector.go deleted file mode 100644 index 05ad4186..00000000 --- a/internal/view/port_selector.go +++ /dev/null @@ -1,34 +0,0 @@ -package view - -import ( - "github.com/derailed/tview" - "github.com/gdamore/tcell" -) - -type portSelector struct { - title, port string - ok, cancel func() -} - -func (p *portSelector) show(app *App) { - f := tview.NewForm() - f.SetItemPadding(0) - f.SetButtonsAlign(tview.AlignCenter). - SetButtonBackgroundColor(tview.Styles.PrimitiveBackgroundColor). - SetButtonTextColor(tview.Styles.PrimaryTextColor). - SetLabelColor(tcell.ColorAqua). - SetFieldTextColor(tcell.ColorOrange) - - f1 := p.port - f.AddInputField("Pod Port:", f1, 20, nil, func(changed string) { - f1 = changed - }) - - f.AddButton("OK", p.ok) - f.AddButton("Cancel", p.cancel) - - modal := tview.NewModalForm("<"+p.title+">", f) - modal.SetDoneFunc(func(_ int, b string) { - p.cancel() - }) -} diff --git a/internal/view/rbac.go b/internal/view/rbac.go index 4d63c357..8bf21c35 100644 --- a/internal/view/rbac.go +++ b/internal/view/rbac.go @@ -147,7 +147,7 @@ func (r *Rbac) refresh() { if r.app.Conn() == nil { return } - data, err := r.reconcile(r.ActiveNS(), r.roleName, r.roleType) + data, err := r.reconcile(r.roleName, r.roleType) if err != nil { log.Error().Err(err).Msgf("Refresh for %s:%d", r.roleName, r.roleType) r.app.Flash().Err(err) @@ -179,10 +179,10 @@ func (r *Rbac) backCmd(evt *tcell.EventKey) *tcell.EventKey { return r.app.PrevCmd(evt) } -func (r *Rbac) reconcile(ns, name string, kind roleKind) (resource.TableData, error) { +func (r *Rbac) reconcile(name string, kind roleKind) (resource.TableData, error) { var table resource.TableData - evts, err := r.rowEvents(ns, name, kind) + evts, err := r.rowEvents(name, kind) if err != nil { return table, err } @@ -202,7 +202,7 @@ func (r *Rbac) setCache(evts resource.RowEvents) { r.cache = evts } -func (r *Rbac) rowEvents(ns, name string, kind roleKind) (resource.RowEvents, error) { +func (r *Rbac) rowEvents(name string, kind roleKind) (resource.RowEvents, error) { var ( evts resource.RowEvents err error @@ -277,11 +277,6 @@ func (r *Rbac) parseRules(rules []rbacv1.PolicyRule) resource.RowEvents { } func prepRow(res, grp string, verbs []string) resource.Row { - const ( - nameLen = 60 - groupLen = 30 - ) - if grp != resource.NAValue { grp = toGroup(grp) } diff --git a/internal/view/registrar.go b/internal/view/registrar.go index 2f6b6087..a795d44c 100644 --- a/internal/view/registrar.go +++ b/internal/view/registrar.go @@ -88,8 +88,9 @@ func showRBAC(app *App, ns, resource, selection string) { func showCRD(app *App, ns, resource, selection string) { log.Debug().Msgf("Launching CRD %q -- %q -- %q", ns, resource, selection) tokens := strings.Split(selection, ".") - app.gotoResource(tokens[0], true) - + if !app.gotoResource(tokens[0]) { + app.Flash().Errf("Goto %s failed", tokens[0]) + } } func showClusterRole(app *App, ns, resource, selection string) { diff --git a/internal/view/resource.go b/internal/view/resource.go index c03df0aa..9d2b0b23 100644 --- a/internal/view/resource.go +++ b/internal/view/resource.go @@ -2,6 +2,7 @@ package view import ( "context" + "errors" "fmt" "strconv" "strings" @@ -279,7 +280,9 @@ func (r *Resource) editCmd(evt *tcell.EventKey) *tcell.EventKey { if cfg := r.app.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" { args = append(args, "--kubeconfig", *cfg) } - runK(true, r.app, append(args, po)...) + if !runK(true, r.app, append(args, po)...) { + r.app.Flash().Err(errors.New("Edit exec failed")) + } } r.Start() @@ -452,19 +455,19 @@ func (r *Resource) defaultK9sEnv() K9sEnv { ns, n := namespaced(r.masterPage().GetSelectedItem()) ctx, err := r.app.Conn().Config().CurrentContextName() if err != nil { - ctx = "n/a" + ctx = resource.NAValue } cluster, err := r.app.Conn().Config().CurrentClusterName() if err != nil { - cluster = "n/a" + cluster = resource.NAValue } user, err := r.app.Conn().Config().CurrentUserName() if err != nil { - user = "n/a" + user = resource.NAValue } groups, err := r.app.Conn().Config().CurrentGroupNames() if err != nil { - groups = []string{"n/a"} + groups = []string{resource.NAValue} } var cfg string kcfg := r.app.Conn().Config().Flags().KubeConfig diff --git a/internal/view/rs.go b/internal/view/rs.go index 7b503732..7c9df537 100644 --- a/internal/view/rs.go +++ b/internal/view/rs.go @@ -48,20 +48,17 @@ func (r *ReplicaSet) showPods(app *App, _, res, sel string) { app.Flash().Errf("Replicaset failed %s", err) } - rs := s.(*v1.ReplicaSet) + rs, ok := s.(*v1.ReplicaSet) + if !ok { + log.Fatal().Msg("Expecting a valid rs") + } l, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector) if err != nil { app.Flash().Errf("Selector failed %s", err) return } - showPods(app, ns, l.String(), "", r.backCmd) -} - -func (r *ReplicaSet) backCmd(evt *tcell.EventKey) *tcell.EventKey { - r.app.inject(r) - - return nil + showPods(app, ns, l.String(), "") } func (r *ReplicaSet) rollbackCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/scalable_resource.go b/internal/view/scalable_resource.go index b15247b2..a26db069 100644 --- a/internal/view/scalable_resource.go +++ b/internal/view/scalable_resource.go @@ -9,6 +9,7 @@ import ( "github.com/derailed/k9s/internal/ui" "github.com/derailed/tview" "github.com/gdamore/tcell" + "github.com/rs/zerolog/log" ) // ScalableResource represents a resource that can be scaled. @@ -46,7 +47,10 @@ func (s *ScalableResource) scaleCmd(evt *tcell.EventKey) *tcell.EventKey { func (s *ScalableResource) scale(selection string, replicas int) { ns, n := namespaced(selection) - r := s.list.Resource().(resource.Scalable) + r, ok := s.list.Resource().(resource.Scalable) + if !ok { + log.Fatal().Msg("Expecting a valid scalable resource") + } err := r.Scale(ns, n, int32(replicas)) if err != nil { diff --git a/internal/view/screen_dump.go b/internal/view/screen_dump.go index fc8febc6..1fea475d 100644 --- a/internal/view/screen_dump.go +++ b/internal/view/screen_dump.go @@ -18,14 +18,9 @@ import ( "github.com/rs/zerolog/log" ) -const ( - dumpTitle = "Screen Dumps" - dumpTitleFmt = " [mediumvioletred::b]%s([fuchsia::b]%d[fuchsia::-])[mediumvioletred::-] " -) +const dumpTitle = "Screen Dumps" -var ( - dumpHeader = resource.Row{"NAME", "AGE"} -) +var dumpHeader = resource.Row{"NAME", "AGE"} // ScreenDump presents a directory listing viewer. type ScreenDump struct { @@ -35,6 +30,7 @@ type ScreenDump struct { app *App } +// NewScreenDump returns a new viewer. func NewScreenDump(_, _ string, _ resource.List) ResourceViewer { return &ScreenDump{ MasterDetail: NewMasterDetail(dumpTitle, ""), @@ -43,7 +39,7 @@ func NewScreenDump(_, _ string, _ resource.List) ResourceViewer { // Init initializes the viewer. func (s *ScreenDump) Init(ctx context.Context) { - s.app = ctx.Value(ui.KeyApp).(*App) + s.app = mustExtractApp(ctx) s.MasterDetail.Init(ctx) s.registerActions() @@ -101,10 +97,6 @@ func (s *ScreenDump) registerActions() { }) } -func (s *ScreenDump) getTitle() string { - return dumpTitle -} - func (s *ScreenDump) enterCmd(evt *tcell.EventKey) *tcell.EventKey { log.Debug().Msg("Dump enter!") tv := s.masterPage() @@ -131,7 +123,7 @@ func (s *ScreenDump) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { } dir := filepath.Join(config.K9sDumpDir, s.app.Config.K9s.CurrentCluster) - showModal(s.Pages, fmt.Sprintf("Delete screen dump `%s?", sel), "table", func() { + showModal(s.Pages, fmt.Sprintf("Delete screen dump `%s?", sel), func() { if err := os.Remove(filepath.Join(dir, sel)); err != nil { s.app.Flash().Errf("Unable to delete file %s", err) return @@ -143,15 +135,6 @@ func (s *ScreenDump) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -func (s *ScreenDump) backCmd(evt *tcell.EventKey) *tcell.EventKey { - if s.cancelFn != nil { - s.cancelFn() - } - s.SwitchToPage("table") - - return nil -} - func (s *ScreenDump) Hints() model.MenuHints { if s.CurrentPage() == nil { return nil @@ -188,10 +171,6 @@ func (s *ScreenDump) hydrate() resource.TableData { return data } -func (s *ScreenDump) resetTitle() { - s.SetTitle(fmt.Sprintf(dumpTitleFmt, dumpTitle, s.masterPage().GetRowCount()-1)) -} - func (s *ScreenDump) watchDumpDir(ctx context.Context) error { w, err := fsnotify.NewWatcher() if err != nil { @@ -211,7 +190,9 @@ func (s *ScreenDump) watchDumpDir(ctx context.Context) error { return case <-ctx.Done(): log.Debug().Msg("!!!! FS WATCHER DONE!!") - w.Close() + if err := w.Close(); err != nil { + log.Error().Err(err).Msg("Closing dump watcher") + } return } } diff --git a/internal/view/select_list.go b/internal/view/select_list.go index e92f802c..f4294970 100644 --- a/internal/view/select_list.go +++ b/internal/view/select_list.go @@ -4,7 +4,6 @@ import ( "context" "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tview" "github.com/gdamore/tcell" @@ -44,26 +43,12 @@ func (v *selectList) Start() {} func (v *selectList) Stop() {} func (v *selectList) Name() string { return "picker" } -func (v *selectList) back(evt *tcell.EventKey) *tcell.EventKey { - v.parent.Pop() - - return nil -} - // Protocol... func (v *selectList) Pop() { v.parent.Pop() } -func (v *selectList) getList() resource.List { - return v.parent.getList() -} - -func (v *selectList) getSelection() string { - return v.parent.getSelection() -} - // SetActions to handle keyboard events. func (v *selectList) setActions(aa ui.KeyActions) { v.actions = aa diff --git a/internal/view/sts.go b/internal/view/sts.go index 32ff4f8d..3dcb2a5a 100644 --- a/internal/view/sts.go +++ b/internal/view/sts.go @@ -34,8 +34,8 @@ func (s *StatefulSet) extraActions(aa ui.KeyActions) { s.LogResource.extraActions(aa) s.scalableResource.extraActions(aa) s.restartableResource.extraActions(aa) - aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", s.sortColCmd(1, false), false) - aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", s.sortColCmd(2, false), false) + aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", s.sortColCmd(1), false) + aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", s.sortColCmd(2), false) } func (s *StatefulSet) showPods(app *App, _, res, sel string) { @@ -47,7 +47,10 @@ func (s *StatefulSet) showPods(app *App, _, res, sel string) { return } - sts := st.(*v1.StatefulSet) + sts, ok := st.(*v1.StatefulSet) + if !ok { + log.Fatal().Msg("Expecting a valid sts") + } l, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector) if err != nil { log.Error().Err(err).Msgf("Converting selector for StatefulSet %s", sel) @@ -55,5 +58,5 @@ func (s *StatefulSet) showPods(app *App, _, res, sel string) { return } - showPods(app, ns, l.String(), "", s.backCmd) + showPods(app, ns, l.String(), "") } diff --git a/internal/view/subject.go b/internal/view/subject.go index 62f82734..759de894 100644 --- a/internal/view/subject.go +++ b/internal/view/subject.go @@ -2,8 +2,8 @@ package view import ( "context" - "fmt" "reflect" + "strings" "time" "github.com/derailed/k9s/internal/resource" @@ -105,10 +105,6 @@ func (s *Subject) setColorerFn(f ui.ColorerFunc) {} func (s *Subject) setEnterFn(f enterFn) {} func (s *Subject) setDecorateFn(f decorateFn) {} -func (s *Subject) getTitle() string { - return fmt.Sprintf(rbacTitleFmt, "Subject", s.subjectKind) -} - func (s *Subject) SetSubject(n string) { s.subjectKind = mapSubject(n) } @@ -289,22 +285,21 @@ func (s *Subject) namespacedSubjects() (resource.RowEvents, error) { } func mapCmdSubject(subject string) string { - log.Debug().Msgf("!!!!!!Subject %q", subject) switch subject { case "groups": - return "Group" + return group case "sas": - return "ServiceAccount" + return sa default: - return "User" + return user } } func mapFuSubject(subject string) string { - switch subject { - case "Group": + switch strings.ToLower(subject) { + case group: return "g" - case "ServiceAccount": + case sa: return "s" default: return "u" diff --git a/internal/view/svc.go b/internal/view/svc.go index cb712027..a5b43ae4 100644 --- a/internal/view/svc.go +++ b/internal/view/svc.go @@ -68,7 +68,7 @@ func (s *Service) showPods(app *App, _, res, sel string) { } if sv, ok := svc.(*v1.Service); ok { - s.showSvcPods(ns, sv.Spec.Selector, s.backCmd) + s.showSvcPods(ns, sv.Spec.Selector) } } @@ -83,16 +83,6 @@ func (s *Service) logsCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -func (s *Service) backCmd(evt *tcell.EventKey) *tcell.EventKey { - // Reset namespace to what it was - if err := s.app.Config.SetActiveNamespace(s.list.GetNamespace()); err != nil { - log.Error().Err(err).Msg("Unable to set active namespace") - } - s.app.inject(s) - - return nil -} - func (s *Service) benchStopCmd(evt *tcell.EventKey) *tcell.EventKey { if s.bench != nil { log.Debug().Msg(">>> Benchmark canceled!!") @@ -212,10 +202,10 @@ func benchTimedOut(app *App) { }) } -func (s *Service) showSvcPods(ns string, sel map[string]string, a ui.ActionHandler) { +func (s *Service) showSvcPods(ns string, sel map[string]string) { var labels []string for k, v := range sel { labels = append(labels, fmt.Sprintf("%s=%s", k, v)) } - showPods(s.app, ns, strings.Join(labels, ","), "", a) + showPods(s.app, ns, strings.Join(labels, ","), "") } diff --git a/internal/view/table.go b/internal/view/table.go index cf04ebc4..03302f00 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -22,7 +22,7 @@ func NewTable(title string) *Table { } func (t *Table) Init(ctx context.Context) { - t.app = ctx.Value(ui.KeyApp).(*App) + t.app = mustExtractApp(ctx) ctx = context.WithValue(ctx, ui.KeyStyles, t.app.Styles) t.Table.Init(ctx) diff --git a/internal/view/table_helper.go b/internal/view/table_helper.go index 554f2228..bf8d75d6 100644 --- a/internal/view/table_helper.go +++ b/internal/view/table_helper.go @@ -11,6 +11,7 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" + "github.com/rs/zerolog/log" ) func trimCellRelative(t *Table, row, col int) string { @@ -34,15 +35,15 @@ func saveTable(cluster, name string, data resource.TableData) (string, error) { path := filepath.Join(dir, fName) mod := os.O_CREATE | os.O_WRONLY - file, err := os.OpenFile(path, mod, 0644) - defer func() { - if file != nil { - file.Close() - } - }() + file, err := os.OpenFile(path, mod, 0600) if err != nil { return "", err } + defer func() { + if err := file.Close(); err != nil { + log.Error().Err(err).Msg("Closing file") + } + }() w := csv.NewWriter(file) if err := w.Write(data.Header); err != nil { diff --git a/internal/view/yaml.go b/internal/view/yaml.go index 85a3493b..a25580b9 100644 --- a/internal/view/yaml.go +++ b/internal/view/yaml.go @@ -68,16 +68,16 @@ func saveYAML(cluster, name, data string) (string, error) { path := filepath.Join(dir, fName) mod := os.O_CREATE | os.O_WRONLY - file, err := os.OpenFile(path, mod, 0644) - defer func() { - if file != nil { - file.Close() - } - }() + file, err := os.OpenFile(path, mod, 0600) if err != nil { log.Error().Err(err).Msgf("YAML create %s", path) return "", nil } + defer func() { + if err := file.Close(); err != nil { + log.Error().Err(err).Msg("Closing yaml file") + } + }() if _, err := file.Write([]byte(data)); err != nil { return "", err } diff --git a/internal/watch/container.go b/internal/watch/container.go index 1781ec35..6e4b076c 100644 --- a/internal/watch/container.go +++ b/internal/watch/container.go @@ -9,11 +9,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const ( - // ContainerIndex marker for stored containers. - ContainerIndex string = "co" - containerCols = 12 -) +// ContainerIndex marker for stored containers. +const ContainerIndex = "co" // Container tracks container activities. type Container struct { @@ -40,7 +37,10 @@ func (c *Container) Get(fqn string, opts metav1.GetOptions) (interface{}, error) if !ok { return nil, fmt.Errorf("Pod %s not found", fqn) } - po := o.(*v1.Pod) + po, ok := o.(*v1.Pod) + if !ok { + log.Fatal().Msg("Expecting a valid pod") + } cc := make(k8s.Collection, len(po.Spec.InitContainers)+len(po.Spec.Containers)) toContainers(po, cc) @@ -58,7 +58,10 @@ func (c *Container) List(fqn string, opts metav1.ListOptions) k8s.Collection { log.Error().Err(fmt.Errorf("Pod %s not found", fqn)).Msg("Pod") return nil } - po := o.(*v1.Pod) + po, ok := o.(*v1.Pod) + if !ok { + log.Fatal().Msg("Expecting a valid pod") + } cc := make(k8s.Collection, len(po.Spec.InitContainers)+len(po.Spec.Containers)) toContainers(po, cc) diff --git a/internal/watch/helper_test.go b/internal/watch/helper_test.go index fc60c4c5..9bda0b80 100644 --- a/internal/watch/helper_test.go +++ b/internal/watch/helper_test.go @@ -20,9 +20,10 @@ func TestMetaFQN(t *testing.T) { "nons": {metav1.ObjectMeta{Name: "blee"}, "blee"}, } - for k, v := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, v.e, MetaFQN(v.m)) + assert.Equal(t, u.e, MetaFQN(u.m)) }) } } @@ -39,9 +40,10 @@ func TestMxResourceDiff(t *testing.T) { "ncpu": {makeRes("1m", "0Mi"), makeRes("2m", "0Mi"), true}, } - for k, v := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, v.e, resourceDiff(v.r1, v.r2)) + assert.Equal(t, u.e, resourceDiff(u.r1, u.r2)) }) } } @@ -69,7 +71,8 @@ func TestToSelector(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { m := toSelector(u.s) for k, v := range m { @@ -102,7 +105,8 @@ func TestMatchesNode(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, matchesNode(u.n, u.s)) }) @@ -131,7 +135,8 @@ func TestMatchesLabels(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { assert.Equal(t, u.e, matchesLabels(u.l, u.s)) }) diff --git a/internal/watch/informer.go b/internal/watch/informer.go index d36c988b..b869d113 100644 --- a/internal/watch/informer.go +++ b/internal/watch/informer.go @@ -12,13 +12,6 @@ import ( "k8s.io/client-go/tools/cache" ) -const ( - // AllNamespaces designates all namespaces. - allNamespaces = "" - // AllNamespaces designate the special `all` namespace. - allNamespace = "all" -) - type ( // Row represents a collection of string fields. Row []string @@ -54,12 +47,10 @@ type StoreInformer interface { // Informer represents a collection of cluster wide watchers. type Informer struct { - Namespace string - informers map[string]StoreInformer - client k8s.Connection - podInformer *Pod - listenerFn TableListenerFn - initOnce sync.Once + Namespace string + informers map[string]StoreInformer + client k8s.Connection + initOnce sync.Once } // NewInformer creates a new cluster resource informer diff --git a/internal/watch/no.go b/internal/watch/no.go index 005b1db4..399565cc 100644 --- a/internal/watch/no.go +++ b/internal/watch/no.go @@ -9,11 +9,8 @@ import ( "k8s.io/client-go/tools/cache" ) -const ( - // NodeIndex marker for stored nodes. - NodeIndex string = "nodes" - nodeCols = 12 -) +// NodeIndex marker for stored nodes. +const NodeIndex = "nodes" // Node tracks node activities. type Node struct { diff --git a/internal/watch/no_mx.go b/internal/watch/no_mx.go index 1e4972d7..d5b509bc 100644 --- a/internal/watch/no_mx.go +++ b/internal/watch/no_mx.go @@ -151,17 +151,24 @@ func (n *nodeMxWatcher) update(list *mv1beta1.NodeMetricsList, notify bool) { fqn := MetaFQN(list.Items[i].ObjectMeta) fqns[fqn] = &list.Items[i] } + n.checkDeletes(fqns, notify) + n.checkAdds(fqns, notify) +} + +func (n *nodeMxWatcher) checkDeletes(m map[string]runtime.Object, notify bool) { for k, v := range n.cache { - if _, ok := fqns[k]; !ok { - if notify { - if err := n.notify(watch.Event{Type: watch.Deleted, Object: v}); err != nil { - return - } - } - delete(n.cache, k) + if _, ok := m[k]; ok { + continue + } + delete(n.cache, k) + if notify && n.notify(watch.Event{Type: watch.Deleted, Object: v}) != nil { + return } } - for k, v := range fqns { +} + +func (n *nodeMxWatcher) checkAdds(m map[string]runtime.Object, notify bool) { + for k, v := range m { kind := watch.Added if v1, ok := n.cache[k]; ok { if !resourceDiff(v1.(*mv1beta1.NodeMetrics).Usage, v.(*mv1beta1.NodeMetrics).Usage) { @@ -169,11 +176,9 @@ func (n *nodeMxWatcher) update(list *mv1beta1.NodeMetricsList, notify bool) { } kind = watch.Modified } - if notify { - if err := n.notify(watch.Event{Type: kind, Object: v}); err != nil { - return - } - } n.cache[k] = v + if notify && n.notify(watch.Event{Type: kind, Object: v}) != nil { + return + } } } diff --git a/internal/watch/no_mx_test.go b/internal/watch/no_mx_test.go index 88b9deac..c2e4bba9 100644 --- a/internal/watch/no_mx_test.go +++ b/internal/watch/no_mx_test.go @@ -39,13 +39,13 @@ func TestNodeMXUpdate(t *testing.T) { mxx := &mv1beta1.NodeMetricsList{ Items: []mv1beta1.NodeMetrics{ - *makeNodeMX("n1", "10m", "10Mi"), + *makeNodeMX("n2", "10m", "10Mi"), }, } no.update(mxx, false) - assert.Equal(t, toQty("10m"), *no.cache["n1"].(*mv1beta1.NodeMetrics).Usage.Cpu()) - assert.Equal(t, toQty("10Mi"), *no.cache["n1"].(*mv1beta1.NodeMetrics).Usage.Memory()) + assert.Equal(t, toQty("10m"), *no.cache["n2"].(*mv1beta1.NodeMetrics).Usage.Cpu()) + assert.Equal(t, toQty("10Mi"), *no.cache["n2"].(*mv1beta1.NodeMetrics).Usage.Memory()) } func TestNodeMXUpdateNoChange(t *testing.T) { diff --git a/internal/watch/pod.go b/internal/watch/pod.go index c7647184..6dc06b7a 100644 --- a/internal/watch/pod.go +++ b/internal/watch/pod.go @@ -11,11 +11,8 @@ import ( "k8s.io/client-go/tools/cache" ) -const ( - // PodIndex marker for stored pods. - PodIndex string = "pods" - podCols = 11 -) +// PodIndex marker for stored pods. +const PodIndex = "pods" // Connection represents an client api server connection. type Connection k8s.Connection @@ -40,7 +37,10 @@ func (p *Pod) List(ns string, opts metav1.ListOptions) k8s.Collection { nodeSelector = true } for _, o := range p.GetStore().List() { - pod := o.(*v1.Pod) + pod, ok := o.(*v1.Pod) + if !ok { + panic("expecting pod") + } if ns != "" && pod.Namespace != ns { continue } diff --git a/internal/watch/pod_mx.go b/internal/watch/pod_mx.go index 01c06202..602e733f 100644 --- a/internal/watch/pod_mx.go +++ b/internal/watch/pod_mx.go @@ -42,7 +42,10 @@ func NewPodMetrics(c k8s.Connection, ns string) *PodMetrics { func (p *PodMetrics) List(ns string, opts metav1.ListOptions) k8s.Collection { var res k8s.Collection for _, o := range p.GetStore().List() { - mx := o.(*mv1beta1.PodMetrics) + mx, ok := o.(*mv1beta1.PodMetrics) + if !ok { + log.Fatal().Msg("Expecting a valid pod metric") + } if ns == "" || mx.Namespace == ns { res = append(res, mx) } @@ -153,18 +156,12 @@ func (p *podMxWatcher) update(list *mv1beta1.PodMetricsList, notify bool) { fqns[fqn] = &list.Items[i] } - for k, v := range p.cache { - if _, ok := fqns[k]; !ok { - if notify { - if err := p.notify(watch.Event{Type: watch.Deleted, Object: v}); err != nil { - return - } - } - delete(p.cache, k) - } - } + p.checkDeletes(fqns, notify) + p.checkAdds(fqns, notify) +} - for k, v := range fqns { +func (p *podMxWatcher) checkAdds(m map[string]runtime.Object, notify bool) { + for k, v := range m { kind := watch.Added if v1, ok := p.cache[k]; ok { if !p.deltas(v1.(*mv1beta1.PodMetrics), v.(*mv1beta1.PodMetrics)) { @@ -172,12 +169,22 @@ func (p *podMxWatcher) update(list *mv1beta1.PodMetricsList, notify bool) { } kind = watch.Modified } - if notify { - if err := p.notify(watch.Event{Type: kind, Object: v}); err != nil { - return - } - } p.cache[k] = v + if notify && p.notify(watch.Event{Type: kind, Object: v}) != nil { + return + } + } +} + +func (p *podMxWatcher) checkDeletes(m map[string]runtime.Object, notify bool) { + for k, v := range p.cache { + if _, ok := m[k]; ok { + continue + } + delete(p.cache, k) + if notify && p.notify(watch.Event{Type: watch.Deleted, Object: v}) != nil { + return + } } } diff --git a/internal/watch/pod_mx_test.go b/internal/watch/pod_mx_test.go index bd88e9ee..de0a9479 100644 --- a/internal/watch/pod_mx_test.go +++ b/internal/watch/pod_mx_test.go @@ -39,15 +39,16 @@ func TestMxDeltas(t *testing.T) { e bool }{ "same": {makePodMxCo("p1", "1m", "0Mi", 1), makePodMxCo("p1", "1m", "0Mi", 1), false}, - "dcpu": {makePodMxCo("p1", "10m", "0Mi", 1), makePodMxCo("p1", "0m", "0Mi", 1), true}, + "dcpu": {makePodMxCo("p1", "10m", "0Mi", 1), makePodMxCo("p2", "0m", "0Mi", 1), true}, "dmem": {makePodMxCo("p1", "0m", "10Mi", 1), makePodMxCo("p1", "0m", "0Mi", 1), true}, "dco": {makePodMxCo("p1", "0m", "10Mi", 1), makePodMxCo("p1", "0m", "0Mi", 2), true}, } var p podMxWatcher - for k, v := range uu { + for k := range uu { + u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, v.e, p.deltas(v.m1, v.m2)) + assert.Equal(t, u.e, p.deltas(u.m1, u.m2)) }) } } @@ -76,12 +77,12 @@ func TestPodMXUpdate(t *testing.T) { mxx := &mv1beta1.PodMetricsList{ Items: []mv1beta1.PodMetrics{ - *makePodMX("p1", "10m", "10Mi"), + *makePodMX("p2", "10m", "10Mi"), }, } po.update(mxx, false) - pmx := po.cache["default/p1"].(*mv1beta1.PodMetrics) + pmx := po.cache["default/p2"].(*mv1beta1.PodMetrics) assert.Equal(t, toQty("10m"), *pmx.Containers[0].Usage.Cpu()) assert.Equal(t, toQty("10Mi"), *pmx.Containers[0].Usage.Memory()) }