From a71f23d3aaf44655156329baf818bb0a1fb915dd Mon Sep 17 00:00:00 2001 From: derailed Date: Mon, 25 Mar 2019 13:58:36 -0600 Subject: [PATCH] added columns for jobs --- Makefile | 3 + change_logs/release_0.3.1.md | 34 ++ cmd/info.go | 19 +- cmd/root.go | 2 +- internal/config/cluster.go | 4 +- internal/config/cluster_test.go | 35 +- internal/config/config.go | 37 +- internal/config/config_test.go | 86 ++-- internal/config/k9s_test.go | 36 +- internal/config/mock_connection_test.go | 444 +++++++++++++++++++++ internal/config/mock_kubesettings_test.go | 31 +- internal/config/ns.go | 8 +- internal/config/ns_test.go | 54 +-- internal/k8s/api.go | 13 + internal/k8s/cluster.go | 12 + internal/k8s/context.go | 22 +- internal/resource/base.go | 17 +- internal/resource/context.go | 14 +- internal/resource/context_test.go | 2 +- internal/resource/cr.go | 4 +- internal/resource/cr_binding.go | 4 +- internal/resource/crd.go | 4 +- internal/resource/cronjob.go | 4 +- internal/resource/cronjob_test.go | 4 +- internal/resource/dp.go | 4 +- internal/resource/ds.go | 4 +- internal/resource/ep.go | 4 +- internal/resource/evt.go | 4 +- internal/resource/hpa.go | 4 +- internal/resource/ing.go | 4 +- internal/resource/job.go | 49 ++- internal/resource/job_test.go | 2 +- internal/resource/mock_clustermeta_test.go | 36 ++ internal/resource/mock_connection_test.go | 37 ++ internal/resource/no.go | 4 +- internal/resource/ns.go | 4 +- internal/resource/pdb.go | 4 +- internal/resource/pod.go | 4 +- internal/resource/pv.go | 4 +- internal/resource/pvc.go | 4 +- internal/resource/rc.go | 4 +- internal/resource/ro.go | 4 +- internal/resource/ro_binding.go | 4 +- internal/resource/rs.go | 4 +- internal/resource/sa.go | 4 +- internal/resource/secret.go | 4 +- internal/resource/sts.go | 4 +- internal/resource/svc.go | 4 +- internal/views/app.go | 10 +- internal/views/cluster_info.go | 7 +- internal/views/command.go | 10 +- internal/views/registrar.go | 12 +- internal/views/resource.go | 6 +- internal/views/splash.go | 6 +- 54 files changed, 894 insertions(+), 254 deletions(-) create mode 100644 change_logs/release_0.3.1.md create mode 100644 internal/config/mock_connection_test.go diff --git a/Makefile b/Makefile index bed78b27..89b65a36 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,9 @@ DATE := $(shell date +%FT%T%Z) default: help +test: ## Run all tests + @go test ./... + cover: ## Run test coverage suite @go test ./... --coverprofile=cov.out @go tool cover --html=cov.out diff --git a/change_logs/release_0.3.1.md b/change_logs/release_0.3.1.md new file mode 100644 index 00000000..8460e3dd --- /dev/null +++ b/change_logs/release_0.3.1.md @@ -0,0 +1,34 @@ +# Release v0.3.1 + +## Notes + +Thank you to all that contributed with flushing out issues with K9s! I'll try +to mark some of these issues as fixed. But if you don't mind grab the latest +rev and see if we're happier with some of the fixes! + +If you've filed an issue please help me verify and close. + +Thank you so much for your support!! + +--- + +## Change Logs + +1. Refactored a lot of code! So please watch for disturbence in the force! +1. Changed cronjob and job aliases names to `cj` and `jo` respectively +1. *JobView*: Added new columns + 1. Completions + 2. Containers + 3. Images +1. *NodeView* Added the following columns: + 1. Available CPU/Mem + 2. Capacity CPU/Mem +1. *NodeView* Added sort fields for cpu and mem + +--- + +## Resolved Bugs + ++ [Issue #133](https://github.com/derailed/k9s/issues/133) ++ [Issue #132](https://github.com/derailed/k9s/issues/132) ++ [Issue #129](https://github.com/derailed/k9s/issues/129) The easiest bug fix to date ;) diff --git a/cmd/info.go b/cmd/info.go index cf2d44cd..a428446c 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -5,6 +5,7 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/printer" + "github.com/derailed/k9s/internal/views" "github.com/spf13/cobra" ) @@ -14,11 +15,19 @@ func infoCmd() *cobra.Command { Short: "Print configuration information", Long: "Print configuration information", Run: func(cmd *cobra.Command, args []string) { - fmt.Printf(printer.Colorize(fmt.Sprintf("%-15s", "Configuration:"), printer.ColorMagenta)) - fmt.Println(printer.Colorize(config.K9sConfigFile, printer.ColorDarkGray)) - - fmt.Printf(printer.Colorize(fmt.Sprintf("%-15s", "Logs:"), printer.ColorMagenta)) - fmt.Println(printer.Colorize(config.K9sLogs, printer.ColorDarkGray)) + printInfo() }, } } + +func printInfo() { + for _, l := range views.LogoSmall { + fmt.Println(printer.Colorize(l, printer.ColorCyan)) + } + fmt.Println() + fmt.Printf(printer.Colorize(fmt.Sprintf("%-15s", "Configuration:"), printer.ColorCyan)) + fmt.Println(printer.Colorize(config.K9sConfigFile, printer.ColorWhite)) + + fmt.Printf(printer.Colorize(fmt.Sprintf("%-15s", "Logs:"), printer.ColorCyan)) + fmt.Println(printer.Colorize(config.K9sLogs, printer.ColorWhite)) +} diff --git a/cmd/root.go b/cmd/root.go index 6a12744b..1573cd96 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -54,7 +54,7 @@ func run(cmd *cobra.Command, args []string) { defer func() { clearScreen() if err := recover(); err != nil { - log.Error().Msgf("%v", err) + log.Error().Msgf("Boom! %v", err) log.Error().Msg(string(debug.Stack())) fmt.Printf(printer.Colorize("Boom!! ", printer.ColorRed)) fmt.Println(printer.Colorize(fmt.Sprintf("%v.", err), printer.ColorDarkGray)) diff --git a/internal/config/cluster.go b/internal/config/cluster.go index 4e1e2ad6..2cf83dd2 100644 --- a/internal/config/cluster.go +++ b/internal/config/cluster.go @@ -1,7 +1,5 @@ package config -import "github.com/derailed/k9s/internal/k8s" - // Cluster tracks K9s cluster configuration. type Cluster struct { Namespace *Namespace `yaml:"namespace"` @@ -14,7 +12,7 @@ func NewCluster() *Cluster { } // Validate a cluster config. -func (c *Cluster) Validate(conn k8s.Connection, ks KubeSettings) { +func (c *Cluster) Validate(conn Connection, ks KubeSettings) { if c.Namespace == nil { c.Namespace = NewNamespace() } diff --git a/internal/config/cluster_test.go b/internal/config/cluster_test.go index f7697c58..53e3c056 100644 --- a/internal/config/cluster_test.go +++ b/internal/config/cluster_test.go @@ -6,16 +6,19 @@ import ( "github.com/derailed/k9s/internal/config" m "github.com/petergtz/pegomock" "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestClusterValidate(t *testing.T) { - setup(t) + mc := NewMockConnection() + m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - ksMock := NewMockKubeSettings() - m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil) + mk := NewMockKubeSettings() + m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2", "default"}) c := config.NewCluster() - c.Validate(ksMock) + c.Validate(mc, mk) assert.Equal(t, "po", c.View.Active) assert.Equal(t, "default", c.Namespace.Active) @@ -24,16 +27,32 @@ func TestClusterValidate(t *testing.T) { } func TestClusterValidateEmpty(t *testing.T) { - setup(t) + mc := NewMockConnection() + m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - ksMock := NewMockKubeSettings() - m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil) + mk := NewMockKubeSettings() + m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2", "default"}) var c config.Cluster - c.Validate(ksMock) + c.Validate(mc, mk) assert.Equal(t, "po", c.View.Active) assert.Equal(t, "default", c.Namespace.Active) assert.Equal(t, 1, len(c.Namespace.Favorites)) assert.Equal(t, []string{"default"}, c.Namespace.Favorites) } + +func namespaces() []v1.Namespace { + return []v1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "fred", + }, + }, + } +} diff --git a/internal/config/config.go b/internal/config/config.go index c1da8269..97ae0e9c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -26,21 +26,26 @@ var ( K9sLogs = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", mustK9sUser())) ) -// KubeSettings exposes kubeconfig context informations. -type KubeSettings interface { - CurrentContextName() (string, error) - CurrentClusterName() (string, error) - CurrentNamespaceName() (string, error) - ClusterNames() ([]string, error) - NamespaceNames(nn []v1.Namespace) []string -} +type ( + // Connection present a kubernetes api server connection. + Connection k8s.Connection -// Config tracks K9s configuration options. -type Config struct { - K9s *K9s `yaml:"k9s"` - client k8s.Connection - settings KubeSettings -} + // KubeSettings exposes kubeconfig context informations. + KubeSettings interface { + CurrentContextName() (string, error) + CurrentClusterName() (string, error) + CurrentNamespaceName() (string, error) + ClusterNames() ([]string, error) + NamespaceNames(nn []v1.Namespace) []string + } + + // Config tracks K9s configuration options. + Config struct { + K9s *K9s `yaml:"k9s"` + client Connection + settings KubeSettings + } +) // NewConfig creates a new default config. func NewConfig(ks KubeSettings) *Config { @@ -135,12 +140,12 @@ func (c *Config) SetActiveView(view string) { } // GetConnection return an api server connection. -func (c *Config) GetConnection() k8s.Connection { +func (c *Config) GetConnection() Connection { return c.client } // SetConnection set an api server connection. -func (c *Config) SetConnection(conn k8s.Connection) { +func (c *Config) SetConnection(conn Connection) { c.client = conn } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index dd6dde38..95333814 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -17,20 +17,20 @@ func init() { } func TestConfigValidate(t *testing.T) { - setup(t) + mc := NewMockConnection() + m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) + mk := NewMockKubeSettings() + m.When(mk.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil) - ksMock := NewMockKubeSettings() - m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil) - m.When(ksMock.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil) - - cfg := config.NewConfig(ksMock) + cfg := config.NewConfig(mk) + cfg.SetConnection(mc) assert.Nil(t, cfg.Load("test_assets/k9s.yml")) cfg.Validate() } func TestConfigLoad(t *testing.T) { - ksMock := NewMockKubeSettings() - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + cfg := config.NewConfig(mk) assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Equal(t, 2, cfg.K9s.RefreshRate) @@ -55,8 +55,8 @@ func TestConfigLoad(t *testing.T) { } func TestConfigCurrentCluster(t *testing.T) { - ksMock := NewMockKubeSettings() - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + cfg := config.NewConfig(mk) assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.NotNil(t, cfg.CurrentCluster()) @@ -65,8 +65,8 @@ func TestConfigCurrentCluster(t *testing.T) { } func TestConfigActiveNamespace(t *testing.T) { - ksMock := NewMockKubeSettings() - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + cfg := config.NewConfig(mk) assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Equal(t, "kube-system", cfg.ActiveNamespace()) @@ -78,8 +78,8 @@ func TestConfigActiveNamespaceBlank(t *testing.T) { } func TestConfigSetActiveNamespace(t *testing.T) { - ksMock := NewMockKubeSettings() - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + cfg := config.NewConfig(mk) assert.Nil(t, cfg.Load("test_assets/k9s.yml")) cfg.SetActiveNamespace("default") @@ -87,8 +87,8 @@ func TestConfigSetActiveNamespace(t *testing.T) { } func TestConfigActiveView(t *testing.T) { - ksMock := NewMockKubeSettings() - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + cfg := config.NewConfig(mk) assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Equal(t, "ctx", cfg.ActiveView()) @@ -100,8 +100,8 @@ func TestConfigActiveViewBlank(t *testing.T) { } func TestConfigSetActiveView(t *testing.T) { - ksMock := NewMockKubeSettings() - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + cfg := config.NewConfig(mk) assert.Nil(t, cfg.Load("test_assets/k9s.yml")) cfg.SetActiveView("po") @@ -109,8 +109,8 @@ func TestConfigSetActiveView(t *testing.T) { } func TestConfigFavNamespaces(t *testing.T) { - ksMock := NewMockKubeSettings() - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + cfg := config.NewConfig(mk) assert.Nil(t, cfg.Load("test_assets/k9s.yml")) expectedNS := []string{"default", "kube-public", "istio-system", "all", "kube-system"} @@ -118,26 +118,30 @@ func TestConfigFavNamespaces(t *testing.T) { } func TestConfigLoadOldCfg(t *testing.T) { - ksMock := NewMockKubeSettings() - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + cfg := config.NewConfig(mk) assert.Nil(t, cfg.Load("test_assets/k9s_old.yml")) } func TestConfigLoadCrap(t *testing.T) { - ksMock := NewMockKubeSettings() - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + cfg := config.NewConfig(mk) assert.NotNil(t, cfg.Load("test_assets/k9s_not_there.yml")) } func TestConfigSaveFile(t *testing.T) { - ksMock := NewMockKubeSettings() - m.When(ksMock.CurrentContextName()).ThenReturn("minikube", nil) - m.When(ksMock.CurrentClusterName()).ThenReturn("minikube", nil) - m.When(ksMock.CurrentNamespaceName()).ThenReturn("default", nil) - m.When(ksMock.ClusterNames()).ThenReturn([]string{"minikube", "fred", "blee"}, nil) - m.When(ksMock.NamespaceNames()).ThenReturn([]string{"default"}, nil) + mc := NewMockConnection() + m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + m.When(mk.CurrentContextName()).ThenReturn("minikube", nil) + m.When(mk.CurrentClusterName()).ThenReturn("minikube", nil) + m.When(mk.CurrentNamespaceName()).ThenReturn("default", nil) + m.When(mk.ClusterNames()).ThenReturn([]string{"minikube", "fred", "blee"}, nil) + m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"}) + + cfg := config.NewConfig(mk) + cfg.SetConnection(mc) cfg.Load("test_assets/k9s.yml") cfg.K9s.RefreshRate = 100 cfg.K9s.LogBufferSize = 500 @@ -155,14 +159,18 @@ func TestConfigSaveFile(t *testing.T) { } func TestConfigReset(t *testing.T) { - ksMock := NewMockKubeSettings() - m.When(ksMock.CurrentContextName()).ThenReturn("blee", nil) - m.When(ksMock.CurrentClusterName()).ThenReturn("blee", nil) - m.When(ksMock.CurrentNamespaceName()).ThenReturn("default", nil) - m.When(ksMock.ClusterNames()).ThenReturn([]string{"blee"}, nil) - m.When(ksMock.NamespaceNames()).ThenReturn([]string{"default"}, nil) + mc := NewMockConnection() + m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + m.When(mk.CurrentContextName()).ThenReturn("blee", nil) + m.When(mk.CurrentClusterName()).ThenReturn("blee", nil) + m.When(mk.CurrentNamespaceName()).ThenReturn("default", nil) + m.When(mk.ClusterNames()).ThenReturn([]string{"blee"}, nil) + m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"}) + + cfg := config.NewConfig(mk) + cfg.SetConnection(mc) cfg.Load("test_assets/k9s.yml") cfg.Reset() cfg.Validate() @@ -178,7 +186,7 @@ func TestConfigReset(t *testing.T) { // Helpers... -func setup(t *testing.T) { +func TestSetup(t *testing.T) { m.RegisterMockTestingT(t) m.RegisterMockFailHandler(func(m string, i ...int) { fmt.Println("Boom!", m, i) diff --git a/internal/config/k9s_test.go b/internal/config/k9s_test.go index 211b2321..819194db 100644 --- a/internal/config/k9s_test.go +++ b/internal/config/k9s_test.go @@ -9,15 +9,14 @@ import ( ) func TestK9sValidate(t *testing.T) { - setup(t) - - ksMock := NewMockKubeSettings() - m.When(ksMock.CurrentContextName()).ThenReturn("ctx1", nil) - m.When(ksMock.CurrentClusterName()).ThenReturn("c1", nil) - m.When(ksMock.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil) + mc := NewMockConnection() + mk := NewMockKubeSettings() + m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil) + m.When(mk.CurrentClusterName()).ThenReturn("c1", nil) + m.When(mk.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil) c := config.NewK9s() - c.Validate(ksMock) + c.Validate(mc, mk) assert.Equal(t, 2, c.RefreshRate) assert.Equal(t, 1000, c.LogBufferSize) @@ -30,15 +29,14 @@ func TestK9sValidate(t *testing.T) { } func TestK9sValidateBlank(t *testing.T) { - setup(t) - - ksMock := NewMockKubeSettings() - m.When(ksMock.CurrentContextName()).ThenReturn("ctx1", nil) - m.When(ksMock.CurrentClusterName()).ThenReturn("c1", nil) - m.When(ksMock.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil) + mc := NewMockConnection() + mk := NewMockKubeSettings() + m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil) + m.When(mk.CurrentClusterName()).ThenReturn("c1", nil) + m.When(mk.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil) var c config.K9s - c.Validate(ksMock) + c.Validate(mc, mk) assert.Equal(t, 2, c.RefreshRate) assert.Equal(t, 1000, c.LogBufferSize) @@ -51,8 +49,6 @@ func TestK9sValidateBlank(t *testing.T) { } func TestK9sActiveClusterZero(t *testing.T) { - setup(t) - c := config.NewK9s() c.CurrentCluster = "fred" cl := c.ActiveCluster() @@ -62,18 +58,14 @@ func TestK9sActiveClusterZero(t *testing.T) { } func TestK9sActiveClusterBlank(t *testing.T) { - setup(t) - var c config.K9s cl := c.ActiveCluster() assert.Nil(t, cl) } func TestK9sActiveCluster(t *testing.T) { - setup(t) - - ksMock := NewMockKubeSettings() - cfg := config.NewConfig(ksMock) + mk := NewMockKubeSettings() + cfg := config.NewConfig(mk) assert.Nil(t, cfg.Load("test_assets/k9s.yml")) cl := cfg.K9s.ActiveCluster() diff --git a/internal/config/mock_connection_test.go b/internal/config/mock_connection_test.go new file mode 100644 index 00000000..edaf7a5a --- /dev/null +++ b/internal/config/mock_connection_test.go @@ -0,0 +1,444 @@ +// Code generated by pegomock. DO NOT EDIT. +// Source: github.com/derailed/k9s/internal/config (interfaces: Connection) + +package config_test + +import ( + k8s "github.com/derailed/k9s/internal/k8s" + pegomock "github.com/petergtz/pegomock" + v1 "k8s.io/api/core/v1" + dynamic "k8s.io/client-go/dynamic" + kubernetes "k8s.io/client-go/kubernetes" + rest "k8s.io/client-go/rest" + versioned "k8s.io/metrics/pkg/client/clientset/versioned" + "reflect" + "time" +) + +type MockConnection struct { + fail func(message string, callerSkip ...int) +} + +func NewMockConnection() *MockConnection { + return &MockConnection{fail: pegomock.GlobalFailHandler} +} + +func (mock *MockConnection) Config() *k8s.Config { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("Config", params, []reflect.Type{reflect.TypeOf((**k8s.Config)(nil)).Elem()}) + var ret0 *k8s.Config + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(*k8s.Config) + } + } + return ret0 +} + +func (mock *MockConnection) DialOrDie() kubernetes.Interface { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("DialOrDie", params, []reflect.Type{reflect.TypeOf((*kubernetes.Interface)(nil)).Elem()}) + var ret0 kubernetes.Interface + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(kubernetes.Interface) + } + } + return ret0 +} + +func (mock *MockConnection) DynDialOrDie() dynamic.Interface { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("DynDialOrDie", params, []reflect.Type{reflect.TypeOf((*dynamic.Interface)(nil)).Elem()}) + var ret0 dynamic.Interface + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(dynamic.Interface) + } + } + return ret0 +} + +func (mock *MockConnection) HasMetrics() bool { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("HasMetrics", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) + var ret0 bool + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(bool) + } + } + return ret0 +} + +func (mock *MockConnection) IsNamespaced(_param0 string) bool { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{_param0} + result := pegomock.GetGenericMockFrom(mock).Invoke("IsNamespaced", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) + var ret0 bool + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(bool) + } + } + return ret0 +} + +func (mock *MockConnection) MXDial() (*versioned.Clientset, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("MXDial", params, []reflect.Type{reflect.TypeOf((**versioned.Clientset)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 *versioned.Clientset + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(*versioned.Clientset) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + +func (mock *MockConnection) NSDialOrDie() dynamic.NamespaceableResourceInterface { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("NSDialOrDie", params, []reflect.Type{reflect.TypeOf((*dynamic.NamespaceableResourceInterface)(nil)).Elem()}) + var ret0 dynamic.NamespaceableResourceInterface + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(dynamic.NamespaceableResourceInterface) + } + } + return ret0 +} + +func (mock *MockConnection) RestConfigOrDie() *rest.Config { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("RestConfigOrDie", params, []reflect.Type{reflect.TypeOf((**rest.Config)(nil)).Elem()}) + var ret0 *rest.Config + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(*rest.Config) + } + } + return ret0 +} + +func (mock *MockConnection) SupportsResource(_param0 string) bool { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{_param0} + result := pegomock.GetGenericMockFrom(mock).Invoke("SupportsResource", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) + var ret0 bool + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(bool) + } + } + return ret0 +} + +func (mock *MockConnection) SwitchContextOrDie(_param0 string) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{_param0} + pegomock.GetGenericMockFrom(mock).Invoke("SwitchContextOrDie", params, []reflect.Type{}) +} + +func (mock *MockConnection) ValidNamespaces() ([]v1.Namespace, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("ValidNamespaces", params, []reflect.Type{reflect.TypeOf((*[]v1.Namespace)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 []v1.Namespace + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].([]v1.Namespace) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + +func (mock *MockConnection) VerifyWasCalledOnce() *VerifierConnection { + return &VerifierConnection{ + mock: mock, + invocationCountMatcher: pegomock.Times(1), + } +} + +func (mock *MockConnection) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierConnection { + return &VerifierConnection{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + } +} + +func (mock *MockConnection) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierConnection { + return &VerifierConnection{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + inOrderContext: inOrderContext, + } +} + +func (mock *MockConnection) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierConnection { + return &VerifierConnection{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + timeout: timeout, + } +} + +type VerifierConnection struct { + mock *MockConnection + invocationCountMatcher pegomock.Matcher + inOrderContext *pegomock.InOrderContext + timeout time.Duration +} + +func (verifier *VerifierConnection) Config() *Connection_Config_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Config", params, verifier.timeout) + return &Connection_Config_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_Config_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_Config_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_Config_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) DialOrDie() *Connection_DialOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DialOrDie", params, verifier.timeout) + return &Connection_DialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_DialOrDie_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_DialOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_DialOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) DynDialOrDie() *Connection_DynDialOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DynDialOrDie", params, verifier.timeout) + return &Connection_DynDialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_DynDialOrDie_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_DynDialOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_DynDialOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) HasMetrics() *Connection_HasMetrics_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout) + return &Connection_HasMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_HasMetrics_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_HasMetrics_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_HasMetrics_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) IsNamespaced(_param0 string) *Connection_IsNamespaced_OngoingVerification { + params := []pegomock.Param{_param0} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "IsNamespaced", params, verifier.timeout) + return &Connection_IsNamespaced_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_IsNamespaced_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_IsNamespaced_OngoingVerification) GetCapturedArguments() string { + _param0 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1] +} + +func (c *Connection_IsNamespaced_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]string, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(string) + } + } + return +} + +func (verifier *VerifierConnection) MXDial() *Connection_MXDial_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "MXDial", params, verifier.timeout) + return &Connection_MXDial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_MXDial_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_MXDial_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_MXDial_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) NSDialOrDie() *Connection_NSDialOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NSDialOrDie", params, verifier.timeout) + return &Connection_NSDialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_NSDialOrDie_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_NSDialOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_NSDialOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) RestConfigOrDie() *Connection_RestConfigOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RestConfigOrDie", params, verifier.timeout) + return &Connection_RestConfigOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_RestConfigOrDie_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_RestConfigOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_RestConfigOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) SupportsResource(_param0 string) *Connection_SupportsResource_OngoingVerification { + params := []pegomock.Param{_param0} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SupportsResource", params, verifier.timeout) + return &Connection_SupportsResource_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_SupportsResource_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_SupportsResource_OngoingVerification) GetCapturedArguments() string { + _param0 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1] +} + +func (c *Connection_SupportsResource_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]string, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(string) + } + } + return +} + +func (verifier *VerifierConnection) SwitchContextOrDie(_param0 string) *Connection_SwitchContextOrDie_OngoingVerification { + params := []pegomock.Param{_param0} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SwitchContextOrDie", params, verifier.timeout) + return &Connection_SwitchContextOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_SwitchContextOrDie_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_SwitchContextOrDie_OngoingVerification) GetCapturedArguments() string { + _param0 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1] +} + +func (c *Connection_SwitchContextOrDie_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]string, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(string) + } + } + return +} + +func (verifier *VerifierConnection) ValidNamespaces() *Connection_ValidNamespaces_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ValidNamespaces", params, verifier.timeout) + return &Connection_ValidNamespaces_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_ValidNamespaces_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_ValidNamespaces_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_ValidNamespaces_OngoingVerification) GetAllCapturedArguments() { +} diff --git a/internal/config/mock_kubesettings_test.go b/internal/config/mock_kubesettings_test.go index 17597add..306dd234 100644 --- a/internal/config/mock_kubesettings_test.go +++ b/internal/config/mock_kubesettings_test.go @@ -5,6 +5,7 @@ package config_test import ( pegomock "github.com/petergtz/pegomock" + v1 "k8s.io/api/core/v1" "reflect" "time" ) @@ -93,23 +94,19 @@ func (mock *MockKubeSettings) CurrentNamespaceName() (string, error) { return ret0, ret1 } -func (mock *MockKubeSettings) NamespaceNames() ([]string, error) { +func (mock *MockKubeSettings) NamespaceNames(_param0 []v1.Namespace) []string { if mock == nil { panic("mock must not be nil. Use myMock := NewMockKubeSettings().") } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("NamespaceNames", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + params := []pegomock.Param{_param0} + result := pegomock.GetGenericMockFrom(mock).Invoke("NamespaceNames", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem()}) var ret0 []string - var ret1 error if len(result) != 0 { if result[0] != nil { ret0 = result[0].([]string) } - if result[1] != nil { - ret1 = result[1].(error) - } } - return ret0, ret1 + return ret0 } func (mock *MockKubeSettings) VerifyWasCalledOnce() *VerifierKubeSettings { @@ -217,8 +214,8 @@ func (c *KubeSettings_CurrentNamespaceName_OngoingVerification) GetCapturedArgum func (c *KubeSettings_CurrentNamespaceName_OngoingVerification) GetAllCapturedArguments() { } -func (verifier *VerifierKubeSettings) NamespaceNames() *KubeSettings_NamespaceNames_OngoingVerification { - params := []pegomock.Param{} +func (verifier *VerifierKubeSettings) NamespaceNames(_param0 []v1.Namespace) *KubeSettings_NamespaceNames_OngoingVerification { + params := []pegomock.Param{_param0} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NamespaceNames", params, verifier.timeout) return &KubeSettings_NamespaceNames_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } @@ -228,8 +225,18 @@ type KubeSettings_NamespaceNames_OngoingVerification struct { methodInvocations []pegomock.MethodInvocation } -func (c *KubeSettings_NamespaceNames_OngoingVerification) GetCapturedArguments() { +func (c *KubeSettings_NamespaceNames_OngoingVerification) GetCapturedArguments() []v1.Namespace { + _param0 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1] } -func (c *KubeSettings_NamespaceNames_OngoingVerification) GetAllCapturedArguments() { +func (c *KubeSettings_NamespaceNames_OngoingVerification) GetAllCapturedArguments() (_param0 [][]v1.Namespace) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([][]v1.Namespace, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.([]v1.Namespace) + } + } + return } diff --git a/internal/config/ns.go b/internal/config/ns.go index fb8c1592..a99882e2 100644 --- a/internal/config/ns.go +++ b/internal/config/ns.go @@ -1,9 +1,7 @@ package config import ( - "github.com/derailed/k9s/internal/k8s" "github.com/rs/zerolog/log" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -28,13 +26,13 @@ func NewNamespace() *Namespace { } // Validate a namespace is setup correctly -func (n *Namespace) Validate(c k8s.Connection, ks KubeSettings) { - nns, err := c.DialOrDie().CoreV1().Namespaces().List(metav1.ListOptions{}) +func (n *Namespace) Validate(c Connection, ks KubeSettings) { + nns, err := c.ValidNamespaces() if err != nil { return } - nn := ks.NamespaceNames(nns.Items) + nn := ks.NamespaceNames(nns) if !n.isAllNamespace() && !InList(nn, n.Active) { log.Debug().Msg("[Config] Validation error active namespace resetting to `default") n.Active = defaultNS diff --git a/internal/config/ns_test.go b/internal/config/ns_test.go index a4161ad0..120a3462 100644 --- a/internal/config/ns_test.go +++ b/internal/config/ns_test.go @@ -1,7 +1,7 @@ package config_test import ( - "errors" + "fmt" "testing" "github.com/derailed/k9s/internal/config" @@ -10,43 +10,43 @@ import ( ) func TestNSValidate(t *testing.T) { - setup(t) + mc := NewMockConnection() + m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) + mk := NewMockKubeSettings() + m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2", "default"}) ns := config.NewNamespace() + ns.Validate(mc, mk) - ksMock := NewMockKubeSettings() - m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil) - - ns.Validate(ksMock) - ksMock.VerifyWasCalledOnce() + mk.VerifyWasCalledOnce() assert.Equal(t, "default", ns.Active) assert.Equal(t, []string{"default"}, ns.Favorites) } func TestNSValidateMissing(t *testing.T) { - setup(t) + mc := NewMockConnection() + m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) + mk := NewMockKubeSettings() + m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2"}) ns := config.NewNamespace() + ns.Validate(mc, mk) - ksMock := NewMockKubeSettings() - m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2"}, nil) - ns.Validate(ksMock) - - ksMock.VerifyWasCalledOnce() + mk.VerifyWasCalledOnce() assert.Equal(t, "default", ns.Active) assert.Equal(t, []string{}, ns.Favorites) } func TestNSValidateNoNS(t *testing.T) { - setup(t) + mc := NewMockConnection() + m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), fmt.Errorf("Crap!")) + mk := NewMockKubeSettings() + m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2"}) ns := config.NewNamespace() + ns.Validate(mc, mk) - ksMock := NewMockKubeSettings() - m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2"}, errors.New("boom")) - ns.Validate(ksMock) - - ksMock.VerifyWasCalledOnce() + mk.VerifyWasCalledOnce() assert.Equal(t, "default", ns.Active) assert.Equal(t, []string{"default"}, ns.Favorites) } @@ -64,12 +64,13 @@ func TestNSSetActive(t *testing.T) { {"ns4", allNS}, } - ksMock := NewMockKubeSettings() - m.When(ksMock.NamespaceNames()).ThenReturn(allNS, nil) + mk := NewMockKubeSettings() + m.When(mk.NamespaceNames(namespaces())).ThenReturn(allNS) ns := config.NewNamespace() for _, u := range uu { - err := ns.SetActive(u.ns, ksMock) + err := ns.SetActive(u.ns, mk) + assert.Nil(t, err) assert.Equal(t, u.ns, ns.Active) assert.Equal(t, u.fav, ns.Favorites) @@ -79,12 +80,13 @@ func TestNSSetActive(t *testing.T) { func TestNSValidateRmFavs(t *testing.T) { allNS := []string{"default", "kube-system"} - ksMock := NewMockKubeSettings() - m.When(ksMock.NamespaceNames()).ThenReturn(allNS, nil) + mc := NewMockConnection() + mk := NewMockKubeSettings() + m.When(mk.NamespaceNames(namespaces())).ThenReturn(allNS) ns := config.NewNamespace() ns.Favorites = []string{"default", "fred", "blee"} + ns.Validate(mc, mk) - ns.Validate(ksMock) - assert.Equal(t, []string{"default"}, ns.Favorites) + assert.Equal(t, []string{"fred"}, ns.Favorites) } diff --git a/internal/k8s/api.go b/internal/k8s/api.go index 84b9f353..c7e13628 100644 --- a/internal/k8s/api.go +++ b/internal/k8s/api.go @@ -3,6 +3,8 @@ package k8s import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -47,6 +49,7 @@ type ( HasMetrics() bool IsNamespaced(n string) bool SupportsResource(group string) bool + ValidNamespaces() ([]v1.Namespace, error) } // APIClient represents a Kubernetes api client. @@ -70,6 +73,16 @@ func InitConnectionOrDie(config *Config, logger zerolog.Logger) *APIClient { return &conn } +// ValidNamespaces returns a collection of valid namespaces. +// Bozo!! filter active? +func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) { + nn, err := a.DialOrDie().CoreV1().Namespaces().List(metav1.ListOptions{}) + if err != nil { + return nil, err + } + return nn.Items, nil +} + // IsNamespaced check on server if given resource is namespaced func (a *APIClient) IsNamespaced(res string) bool { list, _ := a.DialOrDie().Discovery().ServerPreferredResources() diff --git a/internal/k8s/cluster.go b/internal/k8s/cluster.go index 9a9d65ee..9c301274 100644 --- a/internal/k8s/cluster.go +++ b/internal/k8s/cluster.go @@ -2,6 +2,8 @@ package k8s import ( "github.com/rs/zerolog" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Cluster represents a Kubernetes cluster. @@ -55,3 +57,13 @@ func (c *Cluster) UserName() string { } return usr } + +// FetchNodes get all available nodes in the cluster. +func (c *Cluster) FetchNodes() ([]v1.Node, error) { + list, err := c.DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{}) + if err != nil { + return nil, err + } + + return list.Items, nil +} diff --git a/internal/k8s/context.go b/internal/k8s/context.go index 6de1ce13..0a2e8906 100644 --- a/internal/k8s/context.go +++ b/internal/k8s/context.go @@ -7,17 +7,6 @@ import ( "k8s.io/client-go/tools/clientcmd/api" ) -// Switchable represents a switchable resource. -type Switchable interface { - Switch(ctx string) error -} - -// SwitchableResource represents a Kubernetes clusters configurations. -type SwitchableResource interface { - Cruder - Switchable -} - // NamedContext represents a named cluster context. type NamedContext struct { Name string @@ -39,6 +28,8 @@ func (c *NamedContext) MustCurrentContextName() string { return cl } +// ---------------------------------------------------------------------------- + // Context represents a Kubernetes Context. type Context struct { Connection @@ -84,6 +75,15 @@ func (c *Context) Delete(_, n string) error { return c.Config().DelContext(n) } +// MustCurrentContextName return the active context name. +func (c *Context) MustCurrentContextName() string { + cl, err := c.Config().CurrentContextName() + if err != nil { + panic(err) + } + return cl +} + // Switch to another context. func (c *Context) Switch(ctx string) error { c.SwitchContextOrDie(ctx) diff --git a/internal/resource/base.go b/internal/resource/base.go index c2048ac4..8be764cb 100644 --- a/internal/resource/base.go +++ b/internal/resource/base.go @@ -10,12 +10,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions/printers" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" "k8s.io/kubernetes/pkg/kubectl/describe" versioned "k8s.io/kubernetes/pkg/kubectl/describe/versioned" - v "k8s.io/metrics/pkg/client/clientset/versioned" ) type ( @@ -27,18 +23,7 @@ type ( } // Connection represents a Kubenetes apiserver connection. - Connection interface { - Config() *k8s.Config - DialOrDie() kubernetes.Interface - SwitchContextOrDie(ctx string) - NSDialOrDie() dynamic.NamespaceableResourceInterface - RestConfigOrDie() *restclient.Config - MXDial() (*v.Clientset, error) - DynDialOrDie() dynamic.Interface - HasMetrics() bool - IsNamespaced(n string) bool - SupportsResource(group string) bool - } + Connection k8s.Connection // Factory creates new tabular resources. Factory interface { diff --git a/internal/resource/context.go b/internal/resource/context.go index 81fd1268..de54d18f 100644 --- a/internal/resource/context.go +++ b/internal/resource/context.go @@ -6,10 +6,16 @@ import ( ) type ( - // SwitchableResource represents a resource that can be switched. - SwitchableResource interface { + // Switchable represents a switchable resource. + Switchable interface { + Switch(ctx string) error + MustCurrentContextName() string + } + + // SwitchableCruder represents a resource that can be switched. + SwitchableCruder interface { Cruder - k8s.Switchable + Switchable } // Context tracks a kubernetes resource. @@ -50,7 +56,7 @@ func (r *Context) New(i interface{}) Columnar { // Switch out current context. func (r *Context) Switch(c string) error { - return r.Resource.(SwitchableResource).Switch(c) + return r.Resource.(Switchable).Switch(c) } // Marshal the resource to yaml. diff --git a/internal/resource/context_test.go b/internal/resource/context_test.go index 75538cef..8d86e204 100644 --- a/internal/resource/context_test.go +++ b/internal/resource/context_test.go @@ -15,7 +15,7 @@ func NewContextListWithArgs(ns string, ctx *resource.Context) resource.List { return resource.NewList(resource.NotNamespaced, "ctx", ctx, resource.SwitchAccess) } -func NewContextWithArgs(c k8s.Connection, s resource.SwitchableResource) *resource.Context { +func NewContextWithArgs(c k8s.Connection, s resource.SwitchableCruder) *resource.Context { ctx := &resource.Context{Base: resource.NewBase(c, s)} ctx.Factory = ctx return ctx diff --git a/internal/resource/cr.go b/internal/resource/cr.go index 03517045..e42ff812 100644 --- a/internal/resource/cr.go +++ b/internal/resource/cr.go @@ -13,7 +13,7 @@ type ClusterRole struct { } // NewClusterRoleList returns a new resource list. -func NewClusterRoleList(c k8s.Connection, ns string) List { +func NewClusterRoleList(c Connection, ns string) List { return NewList( NotNamespaced, "clusterrole", @@ -23,7 +23,7 @@ func NewClusterRoleList(c k8s.Connection, ns string) List { } // NewClusterRole instantiates a new ClusterRole. -func NewClusterRole(c k8s.Connection) *ClusterRole { +func NewClusterRole(c Connection) *ClusterRole { cr := &ClusterRole{&Base{Connection: c, Resource: k8s.NewClusterRole(c)}, nil} cr.Factory = cr diff --git a/internal/resource/cr_binding.go b/internal/resource/cr_binding.go index fcd2b907..f6ddb96c 100644 --- a/internal/resource/cr_binding.go +++ b/internal/resource/cr_binding.go @@ -13,7 +13,7 @@ type ClusterRoleBinding struct { } // NewClusterRoleBindingList returns a new resource list. -func NewClusterRoleBindingList(c k8s.Connection, _ string) List { +func NewClusterRoleBindingList(c Connection, _ string) List { return NewList( NotNamespaced, "clusterrolebinding", @@ -23,7 +23,7 @@ func NewClusterRoleBindingList(c k8s.Connection, _ string) List { } // NewClusterRoleBinding instantiates a new ClusterRoleBinding. -func NewClusterRoleBinding(c k8s.Connection) *ClusterRoleBinding { +func NewClusterRoleBinding(c Connection) *ClusterRoleBinding { crb := &ClusterRoleBinding{&Base{Connection: c, Resource: k8s.NewClusterRoleBinding(c)}, nil} crb.Factory = crb diff --git a/internal/resource/crd.go b/internal/resource/crd.go index 23fd1219..a559b75e 100644 --- a/internal/resource/crd.go +++ b/internal/resource/crd.go @@ -17,7 +17,7 @@ type CRD struct { } // NewCRDList returns a new resource list. -func NewCRDList(c k8s.Connection, ns string) List { +func NewCRDList(c Connection, ns string) List { return NewList( NotNamespaced, "crd", @@ -27,7 +27,7 @@ func NewCRDList(c k8s.Connection, ns string) List { } // NewCRD instantiates a new CRD. -func NewCRD(c k8s.Connection) *CRD { +func NewCRD(c Connection) *CRD { crd := &CRD{&Base{Connection: c, Resource: k8s.NewCRD(c)}, nil} crd.Factory = crd diff --git a/internal/resource/cronjob.go b/internal/resource/cronjob.go index 839f96a7..14e449cb 100644 --- a/internal/resource/cronjob.go +++ b/internal/resource/cronjob.go @@ -28,7 +28,7 @@ type ( ) // NewCronJobList returns a new resource list. -func NewCronJobList(c k8s.Connection, ns string) List { +func NewCronJobList(c Connection, ns string) List { return NewList( ns, "cronjob", @@ -38,7 +38,7 @@ func NewCronJobList(c k8s.Connection, ns string) List { } // NewCronJob instantiates a new CronJob. -func NewCronJob(c k8s.Connection) *CronJob { +func NewCronJob(c Connection) *CronJob { cj := &CronJob{&Base{Connection: c, Resource: k8s.NewCronJob(c)}, nil} cj.Factory = cj diff --git a/internal/resource/cronjob_test.go b/internal/resource/cronjob_test.go index 11edb4d2..51f21d6a 100644 --- a/internal/resource/cronjob_test.go +++ b/internal/resource/cronjob_test.go @@ -12,7 +12,7 @@ import ( ) func NewCronJobListWithArgs(ns string, r *resource.CronJob) resource.List { - return resource.NewList(ns, "cronjob", r, resource.AllVerbsAccess|resource.DescribeAccess) + return resource.NewList(ns, "cj", r, resource.AllVerbsAccess|resource.DescribeAccess) } func NewCronJobWithArgs(conn k8s.Connection, res resource.Cruder) *resource.CronJob { @@ -31,7 +31,7 @@ func TestCronJobListAccess(t *testing.T) { l.SetNamespace(ns) assert.Equal(t, ns, l.GetNamespace()) - assert.Equal(t, "cronjob", l.GetName()) + assert.Equal(t, "cj", l.GetName()) for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { assert.True(t, l.Access(a)) } diff --git a/internal/resource/dp.go b/internal/resource/dp.go index bf9b2a74..85629e4f 100644 --- a/internal/resource/dp.go +++ b/internal/resource/dp.go @@ -15,7 +15,7 @@ type Deployment struct { } // NewDeploymentList returns a new resource list. -func NewDeploymentList(c k8s.Connection, ns string) List { +func NewDeploymentList(c Connection, ns string) List { return NewList( ns, "deploy", @@ -25,7 +25,7 @@ func NewDeploymentList(c k8s.Connection, ns string) List { } // NewDeployment instantiates a new Deployment. -func NewDeployment(c k8s.Connection) *Deployment { +func NewDeployment(c Connection) *Deployment { d := &Deployment{&Base{Connection: c, Resource: k8s.NewDeployment(c)}, nil} d.Factory = d diff --git a/internal/resource/ds.go b/internal/resource/ds.go index 27d86d4b..8c4ab5d1 100644 --- a/internal/resource/ds.go +++ b/internal/resource/ds.go @@ -15,7 +15,7 @@ type DaemonSet struct { } // NewDaemonSetList returns a new resource list. -func NewDaemonSetList(c k8s.Connection, ns string) List { +func NewDaemonSetList(c Connection, ns string) List { return NewList( ns, "ds", @@ -25,7 +25,7 @@ func NewDaemonSetList(c k8s.Connection, ns string) List { } // NewDaemonSet instantiates a new DaemonSet. -func NewDaemonSet(c k8s.Connection) *DaemonSet { +func NewDaemonSet(c Connection) *DaemonSet { ds := &DaemonSet{&Base{Connection: c, Resource: k8s.NewDaemonSet(c)}, nil} ds.Factory = ds diff --git a/internal/resource/ep.go b/internal/resource/ep.go index e8d03020..0c88f47e 100644 --- a/internal/resource/ep.go +++ b/internal/resource/ep.go @@ -17,7 +17,7 @@ type Endpoints struct { } // NewEndpointsList returns a new resource list. -func NewEndpointsList(c k8s.Connection, ns string) List { +func NewEndpointsList(c Connection, ns string) List { return NewList( ns, "ep", @@ -27,7 +27,7 @@ func NewEndpointsList(c k8s.Connection, ns string) List { } // NewEndpoints instantiates a new Endpoints. -func NewEndpoints(c k8s.Connection) *Endpoints { +func NewEndpoints(c Connection) *Endpoints { ep := &Endpoints{&Base{Connection: c, Resource: k8s.NewEndpoints(c)}, nil} ep.Factory = ep diff --git a/internal/resource/evt.go b/internal/resource/evt.go index 71f223c2..bc25508d 100644 --- a/internal/resource/evt.go +++ b/internal/resource/evt.go @@ -16,7 +16,7 @@ type Event struct { } // NewEventList returns a new resource list. -func NewEventList(c k8s.Connection, ns string) List { +func NewEventList(c Connection, ns string) List { return NewList( ns, "ev", @@ -26,7 +26,7 @@ func NewEventList(c k8s.Connection, ns string) List { } // NewEvent instantiates a new Event. -func NewEvent(c k8s.Connection) *Event { +func NewEvent(c Connection) *Event { ev := &Event{&Base{Connection: c, Resource: k8s.NewEvent(c)}, nil} ev.Factory = ev diff --git a/internal/resource/hpa.go b/internal/resource/hpa.go index f1f4c27f..78df90a0 100644 --- a/internal/resource/hpa.go +++ b/internal/resource/hpa.go @@ -17,7 +17,7 @@ type HPA struct { } // NewHPAList returns a new resource list. -func NewHPAList(c k8s.Connection, ns string) List { +func NewHPAList(c Connection, ns string) List { return NewList( ns, "hpa", @@ -27,7 +27,7 @@ func NewHPAList(c k8s.Connection, ns string) List { } // NewHPA instantiates a new HPA. -func NewHPA(c k8s.Connection) *HPA { +func NewHPA(c Connection) *HPA { hpa := &HPA{&Base{Connection: c, Resource: k8s.NewHPA(c)}, nil} hpa.Factory = hpa diff --git a/internal/resource/ing.go b/internal/resource/ing.go index 37210ead..1112ae54 100644 --- a/internal/resource/ing.go +++ b/internal/resource/ing.go @@ -16,7 +16,7 @@ type Ingress struct { } // NewIngressList returns a new resource list. -func NewIngressList(c k8s.Connection, ns string) List { +func NewIngressList(c Connection, ns string) List { return NewList( ns, "ing", @@ -26,7 +26,7 @@ func NewIngressList(c k8s.Connection, ns string) List { } // NewIngress instantiates a new Ingress. -func NewIngress(c k8s.Connection) *Ingress { +func NewIngress(c Connection) *Ingress { ing := &Ingress{&Base{Connection: c, Resource: k8s.NewIngress(c)}, nil} ing.Factory = ing diff --git a/internal/resource/job.go b/internal/resource/job.go index c4a95f39..ddeaa3bb 100644 --- a/internal/resource/job.go +++ b/internal/resource/job.go @@ -4,22 +4,24 @@ import ( "bufio" "context" "fmt" + "strings" "time" "github.com/derailed/k9s/internal/k8s" "github.com/rs/zerolog/log" - v1 "k8s.io/api/batch/v1" + batchv1 "k8s.io/api/batch/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/duration" ) // Job tracks a kubernetes resource. type Job struct { *Base - instance *v1.Job + instance *batchv1.Job } // NewJobList returns a new resource list. -func NewJobList(c k8s.Connection, ns string) List { +func NewJobList(c Connection, ns string) List { return NewList( ns, "job", @@ -29,7 +31,7 @@ func NewJobList(c k8s.Connection, ns string) List { } // NewJob instantiates a new Job. -func NewJob(c k8s.Connection) *Job { +func NewJob(c Connection) *Job { j := &Job{&Base{Connection: c, Resource: k8s.NewJob(c)}, nil} j.Factory = j @@ -40,9 +42,9 @@ func NewJob(c k8s.Connection) *Job { func (r *Job) New(i interface{}) Columnar { c := NewJob(r.Connection) switch instance := i.(type) { - case *v1.Job: + case *batchv1.Job: c.instance = instance - case v1.Job: + case batchv1.Job: c.instance = &instance default: log.Fatal().Msgf("unknown Job type %#v", i) @@ -60,7 +62,7 @@ func (r *Job) Marshal(path string) (string, error) { return "", err } - jo := i.(*v1.Job) + jo := i.(*batchv1.Job) jo.TypeMeta.APIVersion = "extensions/v1beta1" jo.TypeMeta.Kind = "Job" @@ -121,7 +123,7 @@ func (*Job) Header(ns string) Row { hh = append(hh, "NAMESPACE") } - return append(hh, "NAME", "COMPLETIONS", "DURATION", "AGE") + return append(hh, "NAME", "COMPLETIONS", "DURATION", "CONTAINERS", "IMAGES", "AGE") } // Fields retrieves displayable fields. @@ -133,10 +135,14 @@ func (r *Job) Fields(ns string) Row { ff = append(ff, i.Namespace) } + cc, ii := r.toContainers(i.Spec.Template.Spec) + return append(ff, i.Name, r.toCompletion(i.Spec, i.Status), r.toDuration(i.Status), + cc, + ii, toAge(i.ObjectMeta.CreationTimestamp), ) } @@ -144,7 +150,30 @@ func (r *Job) Fields(ns string) Row { // ---------------------------------------------------------------------------- // Helpers... -func (*Job) toCompletion(spec v1.JobSpec, status v1.JobStatus) (s string) { +func (*Job) toContainers(p v1.PodSpec) (string, string) { + cc := make([]string, 0, len(p.InitContainers)+len(p.Containers)) + ii := make([]string, 0, len(cc)) + for _, c := range p.InitContainers { + cc = append(cc, c.Name) + ii = append(ii, c.Image) + } + for _, c := range p.Containers { + cc = append(cc, c.Name) + ii = append(ii, c.Image) + } + + // Limit to 2 of each... + if len(cc) > 2 { + cc = append(cc[:2], "...") + } + if len(ii) > 2 { + ii = append(ii[:2], "...") + } + + return strings.Join(cc, ","), strings.Join(ii, ",") +} + +func (*Job) toCompletion(spec batchv1.JobSpec, status batchv1.JobStatus) (s string) { if spec.Completions != nil { return fmt.Sprintf("%d/%d", status.Succeeded, *spec.Completions) } @@ -159,7 +188,7 @@ func (*Job) toCompletion(spec v1.JobSpec, status v1.JobStatus) (s string) { return fmt.Sprintf("%d/1", status.Succeeded) } -func (*Job) toDuration(status v1.JobStatus) string { +func (*Job) toDuration(status batchv1.JobStatus) string { switch { case status.StartTime == nil: case status.CompletionTime == nil: diff --git a/internal/resource/job_test.go b/internal/resource/job_test.go index 4a5374e7..1501bbc3 100644 --- a/internal/resource/job_test.go +++ b/internal/resource/job_test.go @@ -70,7 +70,7 @@ func TestJobListData(t *testing.T) { assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, "blee", l.GetNamespace()) row := td.Rows["blee/fred"] - assert.Equal(t, 4, len(row.Deltas)) + assert.Equal(t, 6, len(row.Deltas)) for _, d := range row.Deltas { assert.Equal(t, "", d) } diff --git a/internal/resource/mock_clustermeta_test.go b/internal/resource/mock_clustermeta_test.go index f2e17944..4d52a42f 100644 --- a/internal/resource/mock_clustermeta_test.go +++ b/internal/resource/mock_clustermeta_test.go @@ -234,6 +234,25 @@ func (mock *MockClusterMeta) UserName() string { return ret0 } +func (mock *MockClusterMeta) ValidNamespaces() ([]v1.Namespace, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("ValidNamespaces", params, []reflect.Type{reflect.TypeOf((*[]v1.Namespace)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 []v1.Namespace + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].([]v1.Namespace) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + func (mock *MockClusterMeta) Version() (string, error) { if mock == nil { panic("mock must not be nil. Use myMock := NewMockClusterMeta().") @@ -558,6 +577,23 @@ func (c *ClusterMeta_UserName_OngoingVerification) GetCapturedArguments() { func (c *ClusterMeta_UserName_OngoingVerification) GetAllCapturedArguments() { } +func (verifier *VerifierClusterMeta) ValidNamespaces() *ClusterMeta_ValidNamespaces_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ValidNamespaces", params, verifier.timeout) + return &ClusterMeta_ValidNamespaces_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_ValidNamespaces_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_ValidNamespaces_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_ValidNamespaces_OngoingVerification) GetAllCapturedArguments() { +} + func (verifier *VerifierClusterMeta) Version() *ClusterMeta_Version_OngoingVerification { params := []pegomock.Param{} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Version", params, verifier.timeout) diff --git a/internal/resource/mock_connection_test.go b/internal/resource/mock_connection_test.go index b6c6a635..381cfe9b 100644 --- a/internal/resource/mock_connection_test.go +++ b/internal/resource/mock_connection_test.go @@ -6,6 +6,7 @@ package resource_test import ( k8s "github.com/derailed/k9s/internal/k8s" pegomock "github.com/petergtz/pegomock" + v1 "k8s.io/api/core/v1" dynamic "k8s.io/client-go/dynamic" kubernetes "k8s.io/client-go/kubernetes" rest "k8s.io/client-go/rest" @@ -169,6 +170,25 @@ func (mock *MockConnection) SwitchContextOrDie(_param0 string) { pegomock.GetGenericMockFrom(mock).Invoke("SwitchContextOrDie", params, []reflect.Type{}) } +func (mock *MockConnection) ValidNamespaces() ([]v1.Namespace, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("ValidNamespaces", params, []reflect.Type{reflect.TypeOf((*[]v1.Namespace)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 []v1.Namespace + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].([]v1.Namespace) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + func (mock *MockConnection) VerifyWasCalledOnce() *VerifierConnection { return &VerifierConnection{ mock: mock, @@ -405,3 +425,20 @@ func (c *Connection_SwitchContextOrDie_OngoingVerification) GetAllCapturedArgume } return } + +func (verifier *VerifierConnection) ValidNamespaces() *Connection_ValidNamespaces_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ValidNamespaces", params, verifier.timeout) + return &Connection_ValidNamespaces_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_ValidNamespaces_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_ValidNamespaces_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_ValidNamespaces_OngoingVerification) GetAllCapturedArguments() { +} diff --git a/internal/resource/no.go b/internal/resource/no.go index f2416719..f02b9e00 100644 --- a/internal/resource/no.go +++ b/internal/resource/no.go @@ -24,7 +24,7 @@ type Node struct { } // NewNodeList returns a new resource list. -func NewNodeList(c k8s.Connection, mx MetricsServer, ns string) List { +func NewNodeList(c Connection, mx MetricsServer, ns string) List { return NewList( NotNamespaced, "no", @@ -34,7 +34,7 @@ func NewNodeList(c k8s.Connection, mx MetricsServer, ns string) List { } // NewNode instantiates a new Node. -func NewNode(c k8s.Connection, mx MetricsServer) *Node { +func NewNode(c Connection, mx MetricsServer) *Node { n := &Node{&Base{Connection: c, Resource: k8s.NewNode(c)}, nil, mx, k8s.NodeMetrics{}} n.Factory = n diff --git a/internal/resource/ns.go b/internal/resource/ns.go index 506a38ca..9c06be51 100644 --- a/internal/resource/ns.go +++ b/internal/resource/ns.go @@ -13,7 +13,7 @@ type Namespace struct { } // NewNamespaceList returns a new resource list. -func NewNamespaceList(c k8s.Connection, ns string) List { +func NewNamespaceList(c Connection, ns string) List { return NewList( NotNamespaced, "ns", @@ -23,7 +23,7 @@ func NewNamespaceList(c k8s.Connection, ns string) List { } // NewNamespace instantiates a new Namespace. -func NewNamespace(c k8s.Connection) *Namespace { +func NewNamespace(c Connection) *Namespace { n := &Namespace{&Base{Connection: c, Resource: k8s.NewNamespace(c)}, nil} n.Factory = n diff --git a/internal/resource/pdb.go b/internal/resource/pdb.go index fb28b9c6..0eccb96d 100644 --- a/internal/resource/pdb.go +++ b/internal/resource/pdb.go @@ -16,7 +16,7 @@ type PodDisruptionBudget struct { } // NewPDBList returns a new resource list. -func NewPDBList(c k8s.Connection, ns string) List { +func NewPDBList(c Connection, ns string) List { return NewList( ns, "pdb", @@ -26,7 +26,7 @@ func NewPDBList(c k8s.Connection, ns string) List { } // NewPDB instantiates a new PDB. -func NewPDB(c k8s.Connection) *PodDisruptionBudget { +func NewPDB(c Connection) *PodDisruptionBudget { p := &PodDisruptionBudget{&Base{Connection: c, Resource: k8s.NewPodDisruptionBudget(c)}, nil} p.Factory = p diff --git a/internal/resource/pod.go b/internal/resource/pod.go index 3756f396..b042e796 100644 --- a/internal/resource/pod.go +++ b/internal/resource/pod.go @@ -44,7 +44,7 @@ type ( ) // NewPodList returns a new resource list. -func NewPodList(c k8s.Connection, mx MetricsServer, ns string) List { +func NewPodList(c Connection, mx MetricsServer, ns string) List { return NewList( ns, "po", @@ -54,7 +54,7 @@ func NewPodList(c k8s.Connection, mx MetricsServer, ns string) List { } // NewPod instantiates a new Pod. -func NewPod(c k8s.Connection, mx MetricsServer) *Pod { +func NewPod(c Connection, mx MetricsServer) *Pod { p := &Pod{&Base{Connection: c, Resource: k8s.NewPod(c)}, nil, mx, k8s.PodMetrics{}} p.Factory = p diff --git a/internal/resource/pv.go b/internal/resource/pv.go index d91dd68d..dba824e6 100644 --- a/internal/resource/pv.go +++ b/internal/resource/pv.go @@ -16,7 +16,7 @@ type PV struct { } // NewPVList returns a new resource list. -func NewPVList(c k8s.Connection, ns string) List { +func NewPVList(c Connection, ns string) List { return NewList( NotNamespaced, "pv", @@ -26,7 +26,7 @@ func NewPVList(c k8s.Connection, ns string) List { } // NewPV instantiates a new PV. -func NewPV(c k8s.Connection) *PV { +func NewPV(c Connection) *PV { p := &PV{&Base{Connection: c, Resource: k8s.NewPV(c)}, nil} p.Factory = p diff --git a/internal/resource/pvc.go b/internal/resource/pvc.go index d25ab700..5d3a9265 100644 --- a/internal/resource/pvc.go +++ b/internal/resource/pvc.go @@ -13,7 +13,7 @@ type PVC struct { } // NewPVCList returns a new resource list. -func NewPVCList(c k8s.Connection, ns string) List { +func NewPVCList(c Connection, ns string) List { return NewList( ns, "pvc", @@ -23,7 +23,7 @@ func NewPVCList(c k8s.Connection, ns string) List { } // NewPVC instantiates a new PVC. -func NewPVC(c k8s.Connection) *PVC { +func NewPVC(c Connection) *PVC { p := &PVC{&Base{Connection: c, Resource: k8s.NewPVC(c)}, nil} p.Factory = p diff --git a/internal/resource/rc.go b/internal/resource/rc.go index 6e281423..fe28f3a3 100644 --- a/internal/resource/rc.go +++ b/internal/resource/rc.go @@ -15,7 +15,7 @@ type ReplicationController struct { } // NewReplicationControllerList returns a new resource list. -func NewReplicationControllerList(c k8s.Connection, ns string) List { +func NewReplicationControllerList(c Connection, ns string) List { return NewList( ns, "rc", @@ -25,7 +25,7 @@ func NewReplicationControllerList(c k8s.Connection, ns string) List { } // NewReplicationController instantiates a new ReplicationController. -func NewReplicationController(c k8s.Connection) *ReplicationController { +func NewReplicationController(c Connection) *ReplicationController { r := &ReplicationController{&Base{Connection: c, Resource: k8s.NewReplicationController(c)}, nil} r.Factory = r diff --git a/internal/resource/ro.go b/internal/resource/ro.go index 7340cdd7..0d69057b 100644 --- a/internal/resource/ro.go +++ b/internal/resource/ro.go @@ -15,7 +15,7 @@ type Role struct { } // NewRoleList returns a new resource list. -func NewRoleList(c k8s.Connection, ns string) List { +func NewRoleList(c Connection, ns string) List { return NewList( ns, "role", @@ -25,7 +25,7 @@ func NewRoleList(c k8s.Connection, ns string) List { } // NewRole instantiates a new Role. -func NewRole(c k8s.Connection) *Role { +func NewRole(c Connection) *Role { r := &Role{&Base{Connection: c, Resource: k8s.NewRole(c)}, nil} r.Factory = r diff --git a/internal/resource/ro_binding.go b/internal/resource/ro_binding.go index d4fefde6..5788f7d6 100644 --- a/internal/resource/ro_binding.go +++ b/internal/resource/ro_binding.go @@ -15,7 +15,7 @@ type RoleBinding struct { } // NewRoleBindingList returns a new resource list. -func NewRoleBindingList(c k8s.Connection, ns string) List { +func NewRoleBindingList(c Connection, ns string) List { return NewList( ns, "rolebinding", @@ -25,7 +25,7 @@ func NewRoleBindingList(c k8s.Connection, ns string) List { } // NewRoleBinding instantiates a new RoleBinding. -func NewRoleBinding(c k8s.Connection) *RoleBinding { +func NewRoleBinding(c Connection) *RoleBinding { r := &RoleBinding{&Base{Connection: c, Resource: k8s.NewRoleBinding(c)}, nil} r.Factory = r diff --git a/internal/resource/rs.go b/internal/resource/rs.go index e30fc010..3852ece1 100644 --- a/internal/resource/rs.go +++ b/internal/resource/rs.go @@ -15,7 +15,7 @@ type ReplicaSet struct { } // NewReplicaSetList returns a new resource list. -func NewReplicaSetList(c k8s.Connection, ns string) List { +func NewReplicaSetList(c Connection, ns string) List { return NewList( ns, "rs", @@ -25,7 +25,7 @@ func NewReplicaSetList(c k8s.Connection, ns string) List { } // NewReplicaSet instantiates a new ReplicaSet. -func NewReplicaSet(c k8s.Connection) *ReplicaSet { +func NewReplicaSet(c Connection) *ReplicaSet { r := &ReplicaSet{&Base{Connection: c, Resource: k8s.NewReplicaSet(c)}, nil} r.Factory = r diff --git a/internal/resource/sa.go b/internal/resource/sa.go index d771feeb..8f814493 100644 --- a/internal/resource/sa.go +++ b/internal/resource/sa.go @@ -15,7 +15,7 @@ type ServiceAccount struct { } // NewServiceAccountList returns a new resource list. -func NewServiceAccountList(c k8s.Connection, ns string) List { +func NewServiceAccountList(c Connection, ns string) List { return NewList( ns, "sa", @@ -25,7 +25,7 @@ func NewServiceAccountList(c k8s.Connection, ns string) List { } // NewServiceAccount instantiates a new ServiceAccount. -func NewServiceAccount(c k8s.Connection) *ServiceAccount { +func NewServiceAccount(c Connection) *ServiceAccount { s := &ServiceAccount{&Base{Connection: c, Resource: k8s.NewServiceAccount(c)}, nil} s.Factory = s diff --git a/internal/resource/secret.go b/internal/resource/secret.go index 73b81cf1..9dc86df9 100644 --- a/internal/resource/secret.go +++ b/internal/resource/secret.go @@ -15,7 +15,7 @@ type Secret struct { } // NewSecretList returns a new resource list. -func NewSecretList(c k8s.Connection, ns string) List { +func NewSecretList(c Connection, ns string) List { return NewList( ns, "secret", @@ -25,7 +25,7 @@ func NewSecretList(c k8s.Connection, ns string) List { } // NewSecret instantiates a new Secret. -func NewSecret(c k8s.Connection) *Secret { +func NewSecret(c Connection) *Secret { s := &Secret{&Base{Connection: c, Resource: k8s.NewSecret(c)}, nil} s.Factory = s diff --git a/internal/resource/sts.go b/internal/resource/sts.go index ed92f426..312ab7f5 100644 --- a/internal/resource/sts.go +++ b/internal/resource/sts.go @@ -15,7 +15,7 @@ type StatefulSet struct { } // NewStatefulSetList returns a new resource list. -func NewStatefulSetList(c k8s.Connection, ns string) List { +func NewStatefulSetList(c Connection, ns string) List { return NewList( ns, "sts", @@ -25,7 +25,7 @@ func NewStatefulSetList(c k8s.Connection, ns string) List { } // NewStatefulSet instantiates a new StatefulSet. -func NewStatefulSet(c k8s.Connection) *StatefulSet { +func NewStatefulSet(c Connection) *StatefulSet { s := &StatefulSet{&Base{Connection: c, Resource: k8s.NewStatefulSet(c)}, nil} s.Factory = s diff --git a/internal/resource/svc.go b/internal/resource/svc.go index 94a9fd33..a9ae820a 100644 --- a/internal/resource/svc.go +++ b/internal/resource/svc.go @@ -19,7 +19,7 @@ type Service struct { } // NewServiceList returns a new resource list. -func NewServiceList(c k8s.Connection, ns string) List { +func NewServiceList(c Connection, ns string) List { return NewList( ns, "svc", @@ -29,7 +29,7 @@ func NewServiceList(c k8s.Connection, ns string) List { } // NewService instantiates a new Service. -func NewService(c k8s.Connection) *Service { +func NewService(c Connection) *Service { s := &Service{&Base{Connection: c, Resource: k8s.NewService(c)}, nil} s.Factory = s diff --git a/internal/views/app.go b/internal/views/app.go index 31d88426..bf05a982 100644 --- a/internal/views/app.go +++ b/internal/views/app.go @@ -128,12 +128,6 @@ func (a *appView) conn() k8s.Connection { // Run starts the application loop func (a *appView) Run() { - defer func() { - if err := recover(); err != nil { - log.Error().Msgf("%#v", err) - } - }() - go func() { <-time.After(splashTime * time.Second) a.showPage("main") @@ -302,9 +296,9 @@ func logoView() tview.Primitive { v.SetWordWrap(false) v.SetWrap(false) v.SetDynamicColors(true) - for i, s := range logoSmall { + for i, s := range LogoSmall { fmt.Fprintf(v, "[orange::b]%s", s) - if i+1 < len(logoSmall) { + if i+1 < len(LogoSmall) { fmt.Fprintf(v, "\n") } } diff --git a/internal/views/cluster_info.go b/internal/views/cluster_info.go index 8c662e07..2c8224ed 100644 --- a/internal/views/cluster_info.go +++ b/internal/views/cluster_info.go @@ -1,6 +1,7 @@ package views import ( + "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/resource" "github.com/derailed/tview" "github.com/gdamore/tcell" @@ -18,7 +19,7 @@ func newInfoView(app *appView) *clusterInfoView { return &clusterInfoView{ app: app, Table: tview.NewTable(), - cluster: resource.NewCluster(app.conn(), &log.Logger), + cluster: resource.NewCluster(app.conn(), &log.Logger, k8s.NewMetricsServer(app.conn())), } } @@ -79,12 +80,12 @@ func (v *clusterInfoView) refresh() { v.GetCell(row, 1).SetText(v.cluster.Version()) row++ - nodes, err := v.cluster.GetNodes() + nodes, err := v.cluster.FetchNodes() if err != nil { log.Warn().Msgf("ClusterInfo %s", err) return } - mxNodes, err := v.cluster.GetNodesMetrics() + mxNodes, err := v.cluster.FetchNodesMetrics() if err != nil { log.Warn().Msgf("ClusterInfo %s", err) return diff --git a/internal/views/command.go b/internal/views/command.go index 110be059..cf5ed75e 100644 --- a/internal/views/command.go +++ b/internal/views/command.go @@ -3,6 +3,7 @@ package views import ( "fmt" + "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/resource" "github.com/rs/zerolog/log" ) @@ -53,8 +54,15 @@ func (c *command) run(cmd string) bool { return true default: if res, ok := resourceViews()[cmd]; ok { - v = res.viewFn(res.title, c.app, res.listFn(c.app.conn(), resource.DefaultNamespace), res.colorerFn) + var r resource.List + if res.listMxFn != nil { + r = res.listMxFn(c.app.conn(), k8s.NewMetricsServer(c.app.conn()), resource.DefaultNamespace) + } else { + r = res.listFn(c.app.conn(), resource.DefaultNamespace) + } + v = res.viewFn(res.title, c.app, r, res.colorerFn) c.app.flash(flashInfo, fmt.Sprintf("Viewing %s in namespace %s...", res.title, c.app.config.ActiveNamespace())) + log.Debug().Msgf("Running command %s", cmd) c.exec(cmd, v) return true } diff --git a/internal/views/registrar.go b/internal/views/registrar.go index 4dc79524..fa5eed7e 100644 --- a/internal/views/registrar.go +++ b/internal/views/registrar.go @@ -8,7 +8,8 @@ import ( type ( viewFn func(ns string, app *appView, list resource.List, colorer colorerFn) resourceViewer - listFn func(c k8s.Connection, ns string) resource.List + listFn func(c resource.Connection, ns string) resource.List + listMxFn func(c resource.Connection, mx resource.MetricsServer, ns string) resource.List colorerFn func(ns string, evt *resource.RowEvent) tcell.Color resCmd struct { @@ -16,6 +17,7 @@ type ( api string viewFn viewFn listFn listFn + listMxFn listMxFn colorerFn colorerFn } ) @@ -100,7 +102,7 @@ func resourceViews() map[string]resCmd { listFn: resource.NewCRDList, colorerFn: defaultColorer, }, - "cron": { + "cj": { title: "CronJobs", api: "batch", viewFn: newCronJobView, @@ -156,7 +158,7 @@ func resourceViews() map[string]resCmd { listFn: resource.NewIngressList, colorerFn: defaultColorer, }, - "job": { + "jo": { title: "Jobs", api: "batch", viewFn: newJobView, @@ -167,7 +169,7 @@ func resourceViews() map[string]resCmd { title: "Nodes", api: "", viewFn: newNodeView, - listFn: resource.NewNodeList, + listMxFn: resource.NewNodeList, colorerFn: nsColorer, }, "ns": { @@ -188,7 +190,7 @@ func resourceViews() map[string]resCmd { title: "Pods", api: "", viewFn: newPodView, - listFn: resource.NewPodList, + listMxFn: resource.NewPodList, colorerFn: podColorer, }, "pv": { diff --git a/internal/views/resource.go b/internal/views/resource.go index 5a682de5..32f08140 100644 --- a/internal/views/resource.go +++ b/internal/views/resource.go @@ -80,19 +80,17 @@ func (v *resourceView) init(ctx context.Context, ns string) { v.selectedItem, v.selectedNS = noSelection, ns go func(ctx context.Context) { - initTick := refreshDelay for { select { case <-ctx.Done(): log.Debug().Msgf("%s watcher canceled!", v.title) return - case <-time.After(time.Duration(initTick) * time.Second): + case <-time.After(time.Duration(v.app.config.K9s.RefreshRate) * time.Second): v.refresh() - initTick = float64(v.app.config.K9s.RefreshRate) } } }(ctx) - v.refreshActions() + v.refresh() if tv, ok := v.CurrentPage().Item.(*tableView); ok { tv.Select(0, 0) } diff --git a/internal/views/splash.go b/internal/views/splash.go index 18794878..a27bb62f 100644 --- a/internal/views/splash.go +++ b/internal/views/splash.go @@ -13,7 +13,7 @@ const ( product = "Kubernetes CLI Island Style!" ) -var logoSmall = []string{ +var LogoSmall = []string{ ` ____ __.________ `, `| |/ _/ __ \______`, `| < \____ / ___/`, @@ -22,7 +22,7 @@ var logoSmall = []string{ ` \/ \/ `, } -var logo = []string{ +var Logo = []string{ ` ____ __.________ _________ .____ .___ `, `| |/ _/ __ \_____\_ ___ \| | | |`, `| < \____ / ___/ \ \/| | | |`, @@ -63,7 +63,7 @@ func newSplash(rev string) *splashView { } func (v *splashView) layoutLogo(t *tview.TextView) { - logo := strings.Join(logo, "\n[orange::b]") + logo := strings.Join(Logo, "\n[orange::b]") fmt.Fprintf(t, "%s[orange::b]%s\n", strings.Repeat("\n", 2), logo) }