From e2f1626764f59adfae3046a310fbfba05e99fdc2 Mon Sep 17 00:00:00 2001 From: derailed Date: Thu, 4 Jun 2020 18:20:41 -0600 Subject: [PATCH] add pvc usage check + bugz #642 #754 #753 #743 #728 #718 --- internal/client/client.go | 25 ++++++++++++++----------- internal/config/alias.go | 2 +- internal/config/config.go | 18 +++++++++++++++--- internal/config/helpers.go | 4 ++-- internal/config/hotkey.go | 2 +- internal/config/plugin.go | 2 +- internal/config/styles.go | 2 +- internal/config/views.go | 2 +- internal/dao/cluster.go | 4 ++-- internal/dao/dp.go | 17 +++++++++++++++++ internal/dao/ds.go | 8 ++++++++ internal/dao/pod.go | 8 ++++++++ internal/dao/popeye.go | 7 +++---- internal/dao/sts.go | 18 ++++++++++++++++++ internal/ui/config.go | 4 ++-- internal/ui/config_test.go | 3 ++- internal/view/alias_test.go | 2 +- internal/view/app.go | 11 +++++++---- internal/view/cm_test.go | 2 +- internal/view/command.go | 8 ++++++-- internal/view/container_test.go | 2 +- internal/view/context_test.go | 2 +- internal/view/dp_test.go | 2 +- internal/view/ds_test.go | 2 +- internal/view/help.go | 2 +- internal/view/help_test.go | 2 +- internal/view/job.go | 1 + internal/view/ns_test.go | 2 +- internal/view/pf_test.go | 2 +- internal/view/pod_test.go | 2 +- internal/view/pvc.go | 24 +++++++++++++++--------- internal/view/pvc_test.go | 17 +++++++++++++++++ internal/view/rbac_test.go | 2 +- internal/view/reference_test.go | 2 +- internal/view/screen_dump_test.go | 2 +- internal/view/secret_test.go | 2 +- internal/view/sts_test.go | 2 +- internal/view/svc_test.go | 10 +++++++++- internal/view/table.go | 1 + 39 files changed, 168 insertions(+), 62 deletions(-) create mode 100644 internal/view/pvc_test.go diff --git a/internal/client/client.go b/internal/client/client.go index ad15320e..8476340b 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -215,30 +215,32 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) { } // CheckConnectivity return true if api server is cool or false otherwise. -func (a *APIClient) CheckConnectivity() (ok bool) { +func (a *APIClient) CheckConnectivity() bool { a.mx.Lock() defer a.mx.Unlock() defer func() { if err := recover(); err != nil { - ok = false + a.connOK = false } - if !ok { + if !a.connOK { a.clearCache() } - a.connOK = ok }() // Need to reload to pickup any kubeconfig changes. cfg, err := NewConfig(a.config.flags).RESTConfig() if err != nil { - return false + log.Error().Err(err).Msgf("restConfig load failed") + a.connOK = false + return a.connOK } cfg.Timeout = checkConnTimeout client, err := kubernetes.NewForConfig(cfg) if err != nil { log.Error().Err(err).Msgf("Unable to connect to api server") - return + a.connOK = false + return a.connOK } // Check connection @@ -247,12 +249,13 @@ func (a *APIClient) CheckConnectivity() (ok bool) { log.Debug().Msgf("RESETING CON!!") a.reset() } - ok = true + a.connOK = true } else { log.Error().Err(err).Msgf("K9s can't connect to cluster") + a.connOK = false } - return + return a.connOK } // Config return a kubernetes configuration. @@ -385,6 +388,9 @@ func (a *APIClient) MXDial() (*versioned.Clientset, error) { // SwitchContext handles kubeconfig context switches. func (a *APIClient) SwitchContext(name string) error { + a.mx.Lock() + defer a.mx.Unlock() + log.Debug().Msgf("Switching context %q", name) currentCtx, err := a.config.CurrentContextName() if err != nil { @@ -411,9 +417,6 @@ func (a *APIClient) SwitchContext(name string) error { } func (a *APIClient) reset() { - a.mx.Lock() - defer a.mx.Unlock() - a.config.reset() a.cache = cache.NewLRUExpireCache(cacheSize) a.client, a.dClient, a.nsClient, a.mxsClient = nil, nil, nil, nil diff --git a/internal/config/alias.go b/internal/config/alias.go index 929c67b5..d7cab9d7 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -10,7 +10,7 @@ import ( ) // K9sAlias manages K9s aliases. -var K9sAlias = filepath.Join(K9sHome, "alias.yml") +var K9sAlias = filepath.Join(K9sHome(), "alias.yml") // Alias tracks shortname to GVR mappings. type Alias map[string]string diff --git a/internal/config/config.go b/internal/config/config.go index 4795f908..f9f18ee7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,11 +14,14 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" ) +// K9sConfig represents K9s configuration dir env var. +const K9sConfig = "K9SCONFIG" + var ( - // K9sHome represent K9s home directory. - K9sHome = filepath.Join(mustK9sHome(), ".k9s") + // DefaultK9sHome represent K9s home directory. + DefaultK9sHome = filepath.Join(mustK9sHome(), ".k9s") // K9sConfigFile represents K9s config file location. - K9sConfigFile = filepath.Join(K9sHome, "config.yml") + K9sConfigFile = filepath.Join(K9sHome(), "config.yml") // K9sLogs represents K9s log. K9sLogs = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", MustK9sUser())) // K9sDumpDir represents a directory where K9s screen dumps will be persisted. @@ -53,6 +56,15 @@ type ( } ) +// K9sHome returns k9s configs home directory. +func K9sHome() string { + if env := os.Getenv(K9sConfig); env != "" { + return env + } + + return DefaultK9sHome +} + // NewConfig creates a new default config. func NewConfig(ks KubeSettings) *Config { return &Config{K9s: NewK9s(), settings: ks} diff --git a/internal/config/helpers.go b/internal/config/helpers.go index 8bcaaf1a..0916675a 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -40,7 +40,7 @@ func InNSList(nn []interface{}, ns string) bool { func mustK9sHome() string { usr, err := user.Current() if err != nil { - log.Fatal().Err(err).Msg("Die on retriving user home") + log.Fatal().Err(err).Msg("Die on retrieving user home") } return usr.HomeDir } @@ -49,7 +49,7 @@ func mustK9sHome() string { func MustK9sUser() string { usr, err := user.Current() if err != nil { - log.Fatal().Err(err).Msg("Die on retriving user info") + log.Fatal().Err(err).Msg("Die on retrieving user info") } return usr.Username } diff --git a/internal/config/hotkey.go b/internal/config/hotkey.go index e0817f1f..5a6988ea 100644 --- a/internal/config/hotkey.go +++ b/internal/config/hotkey.go @@ -8,7 +8,7 @@ import ( ) // K9sHotKeys manages K9s hotKeys. -var K9sHotKeys = filepath.Join(K9sHome, "hotkey.yml") +var K9sHotKeys = filepath.Join(K9sHome(), "hotkey.yml") // HotKeys represents a collection of plugins. type HotKeys struct { diff --git a/internal/config/plugin.go b/internal/config/plugin.go index 61b893ff..933df3fb 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -8,7 +8,7 @@ import ( ) // K9sPlugins manages K9s plugins. -var K9sPlugins = filepath.Join(K9sHome, "plugin.yml") +var K9sPlugins = filepath.Join(K9sHome(), "plugin.yml") // Plugins represents a collection of plugins. type Plugins struct { diff --git a/internal/config/styles.go b/internal/config/styles.go index 04f55203..067c1876 100644 --- a/internal/config/styles.go +++ b/internal/config/styles.go @@ -10,7 +10,7 @@ import ( ) // K9sStylesFile represents K9s skins file location. -var K9sStylesFile = filepath.Join(K9sHome, "skin.yml") +var K9sStylesFile = filepath.Join(K9sHome(), "skin.yml") // StyleListener represents a skin's listener. type StyleListener interface { diff --git a/internal/config/views.go b/internal/config/views.go index 5f0ee731..a78977c5 100644 --- a/internal/config/views.go +++ b/internal/config/views.go @@ -8,7 +8,7 @@ import ( ) // K9sViewConfigFile represents the location for the views configuration. -var K9sViewConfigFile = filepath.Join(K9sHome, "views.yml") +var K9sViewConfigFile = filepath.Join(K9sHome(), "views.yml") // ViewConfigListener represents a view config listener. type ViewConfigListener interface { diff --git a/internal/dao/cluster.go b/internal/dao/cluster.go index ace3ab69..b4272360 100644 --- a/internal/dao/cluster.go +++ b/internal/dao/cluster.go @@ -65,7 +65,7 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) { } wait, ok := ctx.Value(internal.KeyWait).(bool) if !ok { - return nil, errors.New("expecting context Wait") + log.Error().Msgf("expecting Context Wait Key") } ss := scanners() @@ -105,7 +105,7 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) { // ScanForSARefs scans cluster resources for serviceaccount refs. func ScanForSARefs(ctx context.Context, f Factory) (Refs, error) { defer func(t time.Time) { - log.Debug().Msgf("Cluster Scan %v", time.Since(t)) + log.Debug().Msgf("SA Cluster Scan %v", time.Since(t)) }(time.Now()) fqn, ok := ctx.Value(internal.KeyPath).(string) diff --git a/internal/dao/dp.go b/internal/dao/dp.go index b409a1d8..b4ad9b34 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -197,12 +197,29 @@ func (d *Deployment) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs GVR: d.GVR(), FQN: client.FQN(dp.Namespace, dp.Name), }) + case "v1/persistentvolumeclaims": + if !hasPVC(&dp.Spec.Template.Spec, n) { + continue + } + refs = append(refs, Ref{ + GVR: d.GVR(), + FQN: client.FQN(dp.Namespace, dp.Name), + }) } } return refs, nil } +func hasPVC(spec *v1.PodSpec, name string) bool { + for _, v := range spec.Volumes { + if v.PersistentVolumeClaim != nil && v.PersistentVolumeClaim.ClaimName == name { + return true + } + } + return false +} + func hasConfigMap(spec *v1.PodSpec, name string) bool { for _, c := range spec.InitContainers { if containerHasConfigMap(c, name) { diff --git a/internal/dao/ds.go b/internal/dao/ds.go index a005ed35..78e99fdd 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -211,6 +211,14 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, GVR: d.GVR(), FQN: client.FQN(ds.Namespace, ds.Name), }) + case "v1/persistentvolumeclaims": + if !hasPVC(&ds.Spec.Template.Spec, n) { + continue + } + refs = append(refs, Ref{ + GVR: d.GVR(), + FQN: client.FQN(ds.Namespace, ds.Name), + }) } } diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 5b5518d2..a68bf580 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -301,6 +301,14 @@ func (p *Pod) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error GVR: p.GVR(), FQN: client.FQN(pod.Namespace, pod.Name), }) + case "v1/persistentvolumeclaims": + if !hasPVC(&pod.Spec, n) { + continue + } + refs = append(refs, Ref{ + GVR: p.GVR(), + FQN: client.FQN(pod.Namespace, pod.Name), + }) } } diff --git a/internal/dao/popeye.go b/internal/dao/popeye.go index 371f1233..ec221a1a 100644 --- a/internal/dao/popeye.go +++ b/internal/dao/popeye.go @@ -55,17 +55,16 @@ func (p *Popeye) List(ctx context.Context, _ string) ([]runtime.Object, error) { } }(time.Now()) - flags := config.NewFlags() - js := "json" + flags, js := config.NewFlags(), "json" flags.Output = &js if report, ok := ctx.Value(internal.KeyPath).(string); ok && report != "" { sections := []string{report} flags.Sections = §ions } - spinach := filepath.Join(cfg.K9sHome, "spinach.yml") + spinach := filepath.Join(cfg.K9sHome(), "spinach.yml") if c, err := p.Factory.Client().Config().CurrentContextName(); err == nil { - spinach = filepath.Join(cfg.K9sHome, fmt.Sprintf("%s_spinach.yml", c)) + spinach = filepath.Join(cfg.K9sHome(), fmt.Sprintf("%s_spinach.yml", c)) } if _, err := os.Stat(spinach); err == nil { flags.Spinach = &spinach diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 3e32bca8..b0ea2b0a 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog/log" @@ -196,6 +197,23 @@ func (s *StatefulSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Ref GVR: s.GVR(), FQN: client.FQN(sts.Namespace, sts.Name), }) + case "v1/persistentvolumeclaims": + for _, v := range sts.Spec.VolumeClaimTemplates { + if !strings.HasPrefix(n, v.Name) { + continue + } + refs = append(refs, Ref{ + GVR: s.GVR(), + FQN: client.FQN(sts.Namespace, sts.Name), + }) + } + if !hasPVC(&sts.Spec.Template.Spec, n) { + continue + } + refs = append(refs, Ref{ + GVR: s.GVR(), + FQN: client.FQN(sts.Namespace, sts.Name), + }) } } diff --git a/internal/ui/config.go b/internal/ui/config.go index 772b275f..cc357270 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -117,14 +117,14 @@ func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error // BenchConfig location of the benchmarks configuration file. func BenchConfig(context string) string { - return filepath.Join(config.K9sHome, config.K9sBench+"-"+context+".yml") + return filepath.Join(config.K9sHome(), config.K9sBench+"-"+context+".yml") } // RefreshStyles load for skin configuration changes. func (c *Configurator) RefreshStyles(context string) { c.BenchFile = BenchConfig(context) - clusterSkins := filepath.Join(config.K9sHome, fmt.Sprintf("%s_skin.yml", context)) + clusterSkins := filepath.Join(config.K9sHome(), fmt.Sprintf("%s_skin.yml", context)) if c.Styles == nil { c.Styles = config.NewStyles() } else { diff --git a/internal/ui/config_test.go b/internal/ui/config_test.go index 2eb9006f..a2d94fcd 100644 --- a/internal/ui/config_test.go +++ b/internal/ui/config_test.go @@ -1,6 +1,7 @@ package ui_test import ( + "os" "path/filepath" "testing" @@ -12,7 +13,7 @@ import ( ) func TestBenchConfig(t *testing.T) { - config.K9sHome = "/tmp/blee" + os.Setenv(config.K9sConfig, "/tmp/blee") assert.Equal(t, "/tmp/blee/bench-fred.yml", ui.BenchConfig("fred")) } diff --git a/internal/view/alias_test.go b/internal/view/alias_test.go index 0bf89270..e00df19d 100644 --- a/internal/view/alias_test.go +++ b/internal/view/alias_test.go @@ -23,7 +23,7 @@ func TestAliasNew(t *testing.T) { assert.Nil(t, v.Init(makeContext())) assert.Equal(t, "Aliases", v.Name()) - assert.Equal(t, 5, len(v.Hints())) + assert.Equal(t, 6, len(v.Hints())) } func TestAliasSearch(t *testing.T) { diff --git a/internal/view/app.go b/internal/view/app.go index 0cfc8c27..0e1be14a 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -281,6 +281,8 @@ func (a *App) refreshCluster() { if c != nil { c.Start() } + } else { + a.ClearStatus(true) } } else { atomic.AddInt32(&a.conRetry, 1) @@ -363,6 +365,11 @@ func (a *App) switchCtx(name string, loadPods bool) error { if err := a.command.Reset(true); err != nil { return err } + v := a.Config.ActiveView() + if v == "" || isContextCmd(v) || loadPods { + v = "pod" + a.Config.SetActiveView(v) + } if err := a.Config.Save(); err != nil { log.Error().Err(err).Msg("Config save failed!") } @@ -370,10 +377,6 @@ func (a *App) switchCtx(name string, loadPods bool) error { a.Flash().Infof("Switching context to %s", name) a.ReloadStyles(name) - v := a.Config.ActiveView() - if v == "" || v == "ctx" || v == "context" { - v = "pod" - } if err := a.gotoResource(v, "", true); loadPods && err != nil { a.Flash().Err(err) } diff --git a/internal/view/cm_test.go b/internal/view/cm_test.go index d4371acd..461ff4ba 100644 --- a/internal/view/cm_test.go +++ b/internal/view/cm_test.go @@ -13,5 +13,5 @@ func TestConfigMapNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "ConfigMaps", s.Name()) - assert.Equal(t, 5, len(s.Hints())) + assert.Equal(t, 6, len(s.Hints())) } diff --git a/internal/view/command.go b/internal/view/command.go index d6fdb94a..aee0d39a 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -151,17 +151,21 @@ func (c *Command) defaultCmd() error { tokens := strings.Split(view, " ") cmd := view ns, err := c.app.Conn().Config().CurrentNamespaceName() - if err == nil { + if err == nil && !isContextCmd(tokens[0]) { cmd = tokens[0] + " " + ns } if err := c.run(cmd, "", true); err != nil { - log.Error().Err(err).Msgf("Saved command load failed. Loading default view") + log.Error().Err(err).Msgf("Default run command failed") return c.run("meow", err.Error(), true) } return nil } +func isContextCmd(c string) bool { + return c == "ctx" || c == "context" +} + func (c *Command) specialCmd(cmd, path string) bool { cmds := strings.Split(cmd, " ") switch cmds[0] { diff --git a/internal/view/container_test.go b/internal/view/container_test.go index 8eb22a04..b5027259 100644 --- a/internal/view/container_test.go +++ b/internal/view/container_test.go @@ -13,5 +13,5 @@ func TestContainerNew(t *testing.T) { assert.Nil(t, c.Init(makeCtx())) assert.Equal(t, "Containers", c.Name()) - assert.Equal(t, 16, len(c.Hints())) + assert.Equal(t, 17, len(c.Hints())) } diff --git a/internal/view/context_test.go b/internal/view/context_test.go index 9850249c..dc0f693e 100644 --- a/internal/view/context_test.go +++ b/internal/view/context_test.go @@ -13,5 +13,5 @@ func TestContext(t *testing.T) { assert.Nil(t, ctx.Init(makeCtx())) assert.Equal(t, "Contexts", ctx.Name()) - assert.Equal(t, 3, len(ctx.Hints())) + assert.Equal(t, 4, len(ctx.Hints())) } diff --git a/internal/view/dp_test.go b/internal/view/dp_test.go index 18e94546..d69fa571 100644 --- a/internal/view/dp_test.go +++ b/internal/view/dp_test.go @@ -13,5 +13,5 @@ func TestDeploy(t *testing.T) { assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "Deployments", v.Name()) - assert.Equal(t, 11, len(v.Hints())) + assert.Equal(t, 12, len(v.Hints())) } diff --git a/internal/view/ds_test.go b/internal/view/ds_test.go index 34cfe258..43d45f8b 100644 --- a/internal/view/ds_test.go +++ b/internal/view/ds_test.go @@ -13,5 +13,5 @@ func TestDaemonSet(t *testing.T) { assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "DaemonSets", v.Name()) - assert.Equal(t, 12, len(v.Hints())) + assert.Equal(t, 13, len(v.Hints())) } diff --git a/internal/view/help.go b/internal/view/help.go index 9e862a63..57e1a7a3 100644 --- a/internal/view/help.go +++ b/internal/view/help.go @@ -58,7 +58,7 @@ func (h *Help) Init(ctx context.Context) error { func (h *Help) bindKeys() { h.Actions().Delete(ui.KeySpace, tcell.KeyCtrlSpace, tcell.KeyCtrlS) h.Actions().Set(ui.KeyActions{ - tcell.KeyEscape: ui.NewKeyAction("Back", h.app.PrevCmd, false), + tcell.KeyEscape: ui.NewKeyAction("Back", h.app.PrevCmd, true), ui.KeyHelp: ui.NewKeyAction("Back", h.app.PrevCmd, false), tcell.KeyEnter: ui.NewKeyAction("Back", h.app.PrevCmd, false), }) diff --git a/internal/view/help_test.go b/internal/view/help_test.go index 5949346f..e02ac736 100644 --- a/internal/view/help_test.go +++ b/internal/view/help_test.go @@ -21,7 +21,7 @@ func TestHelp(t *testing.T) { v := view.NewHelp() assert.Nil(t, v.Init(ctx)) - assert.Equal(t, 22, v.GetRowCount()) + assert.Equal(t, 23, v.GetRowCount()) assert.Equal(t, 8, v.GetColumnCount()) assert.Equal(t, "", strings.TrimSpace(v.GetCell(1, 0).Text)) assert.Equal(t, "Attach", strings.TrimSpace(v.GetCell(1, 1).Text)) diff --git a/internal/view/job.go b/internal/view/job.go index 9cf43cfe..3dae4564 100644 --- a/internal/view/job.go +++ b/internal/view/job.go @@ -20,6 +20,7 @@ func NewJob(gvr client.GVR) ResourceViewer { j := Job{ResourceViewer: NewLogsExtender(NewBrowser(gvr), nil)} j.GetTable().SetEnterFn(j.showPods) j.GetTable().SetColorerFn(render.Job{}.ColorerFunc()) + j.GetTable().SetSortCol("AGE", true) return &j } diff --git a/internal/view/ns_test.go b/internal/view/ns_test.go index 6c862ac5..a78b829a 100644 --- a/internal/view/ns_test.go +++ b/internal/view/ns_test.go @@ -13,5 +13,5 @@ func TestNSCleanser(t *testing.T) { assert.Nil(t, ns.Init(makeCtx())) assert.Equal(t, "Namespaces", ns.Name()) - assert.Equal(t, 5, len(ns.Hints())) + assert.Equal(t, 6, len(ns.Hints())) } diff --git a/internal/view/pf_test.go b/internal/view/pf_test.go index eb21f9f4..505cc60b 100644 --- a/internal/view/pf_test.go +++ b/internal/view/pf_test.go @@ -13,5 +13,5 @@ func TestPortForwardNew(t *testing.T) { assert.Nil(t, pf.Init(makeCtx())) assert.Equal(t, "PortForwards", pf.Name()) - assert.Equal(t, 9, len(pf.Hints())) + assert.Equal(t, 10, len(pf.Hints())) } diff --git a/internal/view/pod_test.go b/internal/view/pod_test.go index dd5c43bb..cc1fe784 100644 --- a/internal/view/pod_test.go +++ b/internal/view/pod_test.go @@ -16,7 +16,7 @@ func TestPodNew(t *testing.T) { assert.Nil(t, po.Init(makeCtx())) assert.Equal(t, "Pods", po.Name()) - assert.Equal(t, 21, len(po.Hints())) + assert.Equal(t, 22, len(po.Hints())) } // Helpers... diff --git a/internal/view/pvc.go b/internal/view/pvc.go index 6bb3c542..f3c84f8a 100644 --- a/internal/view/pvc.go +++ b/internal/view/pvc.go @@ -4,6 +4,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" + "github.com/gdamore/tcell" ) // PersistentVolumeClaim represents a PVC custom viewer. @@ -13,20 +14,25 @@ type PersistentVolumeClaim struct { // NewPersistentVolumeClaim returns a new viewer. func NewPersistentVolumeClaim(gvr client.GVR) ResourceViewer { - d := PersistentVolumeClaim{ + v := PersistentVolumeClaim{ ResourceViewer: NewBrowser(gvr), } - d.SetBindKeysFn(d.bindKeys) - d.GetTable().SetColorerFn(render.PersistentVolumeClaim{}.ColorerFunc()) + v.SetBindKeysFn(v.bindKeys) + v.GetTable().SetColorerFn(render.PersistentVolumeClaim{}.ColorerFunc()) - return &d + return &v } -func (d *PersistentVolumeClaim) bindKeys(aa ui.KeyActions) { +func (p *PersistentVolumeClaim) bindKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ - ui.KeyShiftS: ui.NewKeyAction("Sort Status", d.GetTable().SortColCmd("STATUS", true), false), - ui.KeyShiftV: ui.NewKeyAction("Sort Volume", d.GetTable().SortColCmd("VOLUME", true), false), - ui.KeyShiftO: ui.NewKeyAction("Sort StorageClass", d.GetTable().SortColCmd("STORAGECLASS", true), false), - ui.KeyShiftC: ui.NewKeyAction("Sort Capacity", d.GetTable().SortColCmd("CAPACITY", true), false), + ui.KeyU: ui.NewKeyAction("UsedBy", p.refCmd, true), + ui.KeyShiftS: ui.NewKeyAction("Sort Status", p.GetTable().SortColCmd("STATUS", true), false), + ui.KeyShiftV: ui.NewKeyAction("Sort Volume", p.GetTable().SortColCmd("VOLUME", true), false), + ui.KeyShiftO: ui.NewKeyAction("Sort StorageClass", p.GetTable().SortColCmd("STORAGECLASS", true), false), + ui.KeyShiftC: ui.NewKeyAction("Sort Capacity", p.GetTable().SortColCmd("CAPACITY", true), false), }) } + +func (p *PersistentVolumeClaim) refCmd(evt *tcell.EventKey) *tcell.EventKey { + return scanRefs(evt, p.App(), p.GetTable(), "v1/persistentvolumeclaims") +} diff --git a/internal/view/pvc_test.go b/internal/view/pvc_test.go new file mode 100644 index 00000000..15595498 --- /dev/null +++ b/internal/view/pvc_test.go @@ -0,0 +1,17 @@ +package view_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/view" + "github.com/stretchr/testify/assert" +) + +func TestPVCNew(t *testing.T) { + v := view.NewPersistentVolumeClaim(client.NewGVR("v1/persistentvolumeclaims")) + + assert.Nil(t, v.Init(makeCtx())) + assert.Equal(t, "PersistentVolumeClaims", v.Name()) + assert.Equal(t, 10, len(v.Hints())) +} diff --git a/internal/view/rbac_test.go b/internal/view/rbac_test.go index bcf694ac..2c0972c8 100644 --- a/internal/view/rbac_test.go +++ b/internal/view/rbac_test.go @@ -13,5 +13,5 @@ func TestRbacNew(t *testing.T) { assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "Rbac", v.Name()) - assert.Equal(t, 4, len(v.Hints())) + assert.Equal(t, 5, len(v.Hints())) } diff --git a/internal/view/reference_test.go b/internal/view/reference_test.go index e4d8e089..c7e17f37 100644 --- a/internal/view/reference_test.go +++ b/internal/view/reference_test.go @@ -13,5 +13,5 @@ func TestReferenceNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "References", s.Name()) - assert.Equal(t, 3, len(s.Hints())) + assert.Equal(t, 4, len(s.Hints())) } diff --git a/internal/view/screen_dump_test.go b/internal/view/screen_dump_test.go index 0e9c9ad2..0e191f1b 100644 --- a/internal/view/screen_dump_test.go +++ b/internal/view/screen_dump_test.go @@ -13,5 +13,5 @@ func TestScreenDumpNew(t *testing.T) { assert.Nil(t, po.Init(makeCtx())) assert.Equal(t, "ScreenDumps", po.Name()) - assert.Equal(t, 4, len(po.Hints())) + assert.Equal(t, 5, len(po.Hints())) } diff --git a/internal/view/secret_test.go b/internal/view/secret_test.go index d75cd10f..c2d9d4df 100644 --- a/internal/view/secret_test.go +++ b/internal/view/secret_test.go @@ -13,5 +13,5 @@ func TestSecretNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "Secrets", s.Name()) - assert.Equal(t, 6, len(s.Hints())) + assert.Equal(t, 7, len(s.Hints())) } diff --git a/internal/view/sts_test.go b/internal/view/sts_test.go index adaeec13..30a5de22 100644 --- a/internal/view/sts_test.go +++ b/internal/view/sts_test.go @@ -13,5 +13,5 @@ func TestStatefulSetNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "StatefulSets", s.Name()) - assert.Equal(t, 10, len(s.Hints())) + assert.Equal(t, 11, len(s.Hints())) } diff --git a/internal/view/svc_test.go b/internal/view/svc_test.go index 0d7d3a3a..07142fea 100644 --- a/internal/view/svc_test.go +++ b/internal/view/svc_test.go @@ -141,6 +141,14 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) + dao.MetaAccess.RegisterMeta("v1/persistentvolumeclaims", metav1.APIResource{ + Name: "persistentvolumeclaims", + SingularName: "persistentvolumeclaim", + Namespaced: true, + Kind: "PersistentVolumeClaims", + Verbs: []string{"get", "list", "watch", "delete"}, + Categories: []string{"k9s"}, + }) } func TestServiceNew(t *testing.T) { @@ -148,5 +156,5 @@ func TestServiceNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "Services", s.Name()) - assert.Equal(t, 9, len(s.Hints())) + assert.Equal(t, 10, len(s.Hints())) } diff --git a/internal/view/table.go b/internal/view/table.go index 06b6ca47..206d5b47 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -150,6 +150,7 @@ func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey { func (t *Table) bindKeys() { t.Actions().Add(ui.KeyActions{ + ui.KeyHelp: ui.NewKeyAction("Help", t.App().helpCmd, true), ui.KeySpace: ui.NewSharedKeyAction("Mark", t.markCmd, false), tcell.KeyCtrlSpace: ui.NewSharedKeyAction("Mark Range", t.markSpanCmd, false), tcell.KeyCtrlBackslash: ui.NewSharedKeyAction("Marks Clear", t.clearMarksCmd, false),