added columns for jobs

mine
derailed 2019-03-25 13:58:36 -06:00
parent 1562b6255e
commit a71f23d3aa
54 changed files with 894 additions and 254 deletions

View File

@ -6,6 +6,9 @@ DATE := $(shell date +%FT%T%Z)
default: help default: help
test: ## Run all tests
@go test ./...
cover: ## Run test coverage suite cover: ## Run test coverage suite
@go test ./... --coverprofile=cov.out @go test ./... --coverprofile=cov.out
@go tool cover --html=cov.out @go tool cover --html=cov.out

View File

@ -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 ;)

View File

@ -5,6 +5,7 @@ import (
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/printer" "github.com/derailed/k9s/internal/printer"
"github.com/derailed/k9s/internal/views"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -14,11 +15,19 @@ func infoCmd() *cobra.Command {
Short: "Print configuration information", Short: "Print configuration information",
Long: "Print configuration information", Long: "Print configuration information",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
fmt.Printf(printer.Colorize(fmt.Sprintf("%-15s", "Configuration:"), printer.ColorMagenta)) printInfo()
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))
}, },
} }
} }
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))
}

View File

@ -54,7 +54,7 @@ func run(cmd *cobra.Command, args []string) {
defer func() { defer func() {
clearScreen() clearScreen()
if err := recover(); err != nil { if err := recover(); err != nil {
log.Error().Msgf("%v", err) log.Error().Msgf("Boom! %v", err)
log.Error().Msg(string(debug.Stack())) log.Error().Msg(string(debug.Stack()))
fmt.Printf(printer.Colorize("Boom!! ", printer.ColorRed)) fmt.Printf(printer.Colorize("Boom!! ", printer.ColorRed))
fmt.Println(printer.Colorize(fmt.Sprintf("%v.", err), printer.ColorDarkGray)) fmt.Println(printer.Colorize(fmt.Sprintf("%v.", err), printer.ColorDarkGray))

View File

@ -1,7 +1,5 @@
package config package config
import "github.com/derailed/k9s/internal/k8s"
// Cluster tracks K9s cluster configuration. // Cluster tracks K9s cluster configuration.
type Cluster struct { type Cluster struct {
Namespace *Namespace `yaml:"namespace"` Namespace *Namespace `yaml:"namespace"`
@ -14,7 +12,7 @@ func NewCluster() *Cluster {
} }
// Validate a cluster config. // 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 { if c.Namespace == nil {
c.Namespace = NewNamespace() c.Namespace = NewNamespace()
} }

View File

@ -6,16 +6,19 @@ import (
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
m "github.com/petergtz/pegomock" m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
func TestClusterValidate(t *testing.T) { func TestClusterValidate(t *testing.T) {
setup(t) mc := NewMockConnection()
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil) m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2", "default"})
c := config.NewCluster() c := config.NewCluster()
c.Validate(ksMock) c.Validate(mc, mk)
assert.Equal(t, "po", c.View.Active) assert.Equal(t, "po", c.View.Active)
assert.Equal(t, "default", c.Namespace.Active) assert.Equal(t, "default", c.Namespace.Active)
@ -24,16 +27,32 @@ func TestClusterValidate(t *testing.T) {
} }
func TestClusterValidateEmpty(t *testing.T) { func TestClusterValidateEmpty(t *testing.T) {
setup(t) mc := NewMockConnection()
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil) m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2", "default"})
var c config.Cluster var c config.Cluster
c.Validate(ksMock) c.Validate(mc, mk)
assert.Equal(t, "po", c.View.Active) assert.Equal(t, "po", c.View.Active)
assert.Equal(t, "default", c.Namespace.Active) assert.Equal(t, "default", c.Namespace.Active)
assert.Equal(t, 1, len(c.Namespace.Favorites)) assert.Equal(t, 1, len(c.Namespace.Favorites))
assert.Equal(t, []string{"default"}, 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",
},
},
}
}

View File

@ -26,21 +26,26 @@ var (
K9sLogs = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", mustK9sUser())) K9sLogs = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", mustK9sUser()))
) )
// KubeSettings exposes kubeconfig context informations. type (
type KubeSettings interface { // Connection present a kubernetes api server connection.
Connection k8s.Connection
// KubeSettings exposes kubeconfig context informations.
KubeSettings interface {
CurrentContextName() (string, error) CurrentContextName() (string, error)
CurrentClusterName() (string, error) CurrentClusterName() (string, error)
CurrentNamespaceName() (string, error) CurrentNamespaceName() (string, error)
ClusterNames() ([]string, error) ClusterNames() ([]string, error)
NamespaceNames(nn []v1.Namespace) []string NamespaceNames(nn []v1.Namespace) []string
} }
// Config tracks K9s configuration options. // Config tracks K9s configuration options.
type Config struct { Config struct {
K9s *K9s `yaml:"k9s"` K9s *K9s `yaml:"k9s"`
client k8s.Connection client Connection
settings KubeSettings settings KubeSettings
} }
)
// NewConfig creates a new default config. // NewConfig creates a new default config.
func NewConfig(ks KubeSettings) *Config { func NewConfig(ks KubeSettings) *Config {
@ -135,12 +140,12 @@ func (c *Config) SetActiveView(view string) {
} }
// GetConnection return an api server connection. // GetConnection return an api server connection.
func (c *Config) GetConnection() k8s.Connection { func (c *Config) GetConnection() Connection {
return c.client return c.client
} }
// SetConnection set an api server connection. // SetConnection set an api server connection.
func (c *Config) SetConnection(conn k8s.Connection) { func (c *Config) SetConnection(conn Connection) {
c.client = conn c.client = conn
} }

View File

@ -17,20 +17,20 @@ func init() {
} }
func TestConfigValidate(t *testing.T) { 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() cfg := config.NewConfig(mk)
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil) cfg.SetConnection(mc)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil)
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.Validate() cfg.Validate()
} }
func TestConfigLoad(t *testing.T) { func TestConfigLoad(t *testing.T) {
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
cfg := config.NewConfig(ksMock) cfg := config.NewConfig(mk)
assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
assert.Equal(t, 2, cfg.K9s.RefreshRate) assert.Equal(t, 2, cfg.K9s.RefreshRate)
@ -55,8 +55,8 @@ func TestConfigLoad(t *testing.T) {
} }
func TestConfigCurrentCluster(t *testing.T) { func TestConfigCurrentCluster(t *testing.T) {
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
cfg := config.NewConfig(ksMock) cfg := config.NewConfig(mk)
assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
assert.NotNil(t, cfg.CurrentCluster()) assert.NotNil(t, cfg.CurrentCluster())
@ -65,8 +65,8 @@ func TestConfigCurrentCluster(t *testing.T) {
} }
func TestConfigActiveNamespace(t *testing.T) { func TestConfigActiveNamespace(t *testing.T) {
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
cfg := config.NewConfig(ksMock) cfg := config.NewConfig(mk)
assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
assert.Equal(t, "kube-system", cfg.ActiveNamespace()) assert.Equal(t, "kube-system", cfg.ActiveNamespace())
@ -78,8 +78,8 @@ func TestConfigActiveNamespaceBlank(t *testing.T) {
} }
func TestConfigSetActiveNamespace(t *testing.T) { func TestConfigSetActiveNamespace(t *testing.T) {
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
cfg := config.NewConfig(ksMock) cfg := config.NewConfig(mk)
assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.SetActiveNamespace("default") cfg.SetActiveNamespace("default")
@ -87,8 +87,8 @@ func TestConfigSetActiveNamespace(t *testing.T) {
} }
func TestConfigActiveView(t *testing.T) { func TestConfigActiveView(t *testing.T) {
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
cfg := config.NewConfig(ksMock) cfg := config.NewConfig(mk)
assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
assert.Equal(t, "ctx", cfg.ActiveView()) assert.Equal(t, "ctx", cfg.ActiveView())
@ -100,8 +100,8 @@ func TestConfigActiveViewBlank(t *testing.T) {
} }
func TestConfigSetActiveView(t *testing.T) { func TestConfigSetActiveView(t *testing.T) {
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
cfg := config.NewConfig(ksMock) cfg := config.NewConfig(mk)
assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.SetActiveView("po") cfg.SetActiveView("po")
@ -109,8 +109,8 @@ func TestConfigSetActiveView(t *testing.T) {
} }
func TestConfigFavNamespaces(t *testing.T) { func TestConfigFavNamespaces(t *testing.T) {
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
cfg := config.NewConfig(ksMock) cfg := config.NewConfig(mk)
assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
expectedNS := []string{"default", "kube-public", "istio-system", "all", "kube-system"} expectedNS := []string{"default", "kube-public", "istio-system", "all", "kube-system"}
@ -118,26 +118,30 @@ func TestConfigFavNamespaces(t *testing.T) {
} }
func TestConfigLoadOldCfg(t *testing.T) { func TestConfigLoadOldCfg(t *testing.T) {
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
cfg := config.NewConfig(ksMock) cfg := config.NewConfig(mk)
assert.Nil(t, cfg.Load("test_assets/k9s_old.yml")) assert.Nil(t, cfg.Load("test_assets/k9s_old.yml"))
} }
func TestConfigLoadCrap(t *testing.T) { func TestConfigLoadCrap(t *testing.T) {
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
cfg := config.NewConfig(ksMock) cfg := config.NewConfig(mk)
assert.NotNil(t, cfg.Load("test_assets/k9s_not_there.yml")) assert.NotNil(t, cfg.Load("test_assets/k9s_not_there.yml"))
} }
func TestConfigSaveFile(t *testing.T) { func TestConfigSaveFile(t *testing.T) {
ksMock := NewMockKubeSettings() mc := NewMockConnection()
m.When(ksMock.CurrentContextName()).ThenReturn("minikube", nil) m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), 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)
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.Load("test_assets/k9s.yml")
cfg.K9s.RefreshRate = 100 cfg.K9s.RefreshRate = 100
cfg.K9s.LogBufferSize = 500 cfg.K9s.LogBufferSize = 500
@ -155,14 +159,18 @@ func TestConfigSaveFile(t *testing.T) {
} }
func TestConfigReset(t *testing.T) { func TestConfigReset(t *testing.T) {
ksMock := NewMockKubeSettings() mc := NewMockConnection()
m.When(ksMock.CurrentContextName()).ThenReturn("blee", nil) m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), 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)
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.Load("test_assets/k9s.yml")
cfg.Reset() cfg.Reset()
cfg.Validate() cfg.Validate()
@ -178,7 +186,7 @@ func TestConfigReset(t *testing.T) {
// Helpers... // Helpers...
func setup(t *testing.T) { func TestSetup(t *testing.T) {
m.RegisterMockTestingT(t) m.RegisterMockTestingT(t)
m.RegisterMockFailHandler(func(m string, i ...int) { m.RegisterMockFailHandler(func(m string, i ...int) {
fmt.Println("Boom!", m, i) fmt.Println("Boom!", m, i)

View File

@ -9,15 +9,14 @@ import (
) )
func TestK9sValidate(t *testing.T) { func TestK9sValidate(t *testing.T) {
setup(t) mc := NewMockConnection()
mk := NewMockKubeSettings()
ksMock := NewMockKubeSettings() m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil)
m.When(ksMock.CurrentContextName()).ThenReturn("ctx1", nil) m.When(mk.CurrentClusterName()).ThenReturn("c1", nil)
m.When(ksMock.CurrentClusterName()).ThenReturn("c1", nil) m.When(mk.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil)
c := config.NewK9s() c := config.NewK9s()
c.Validate(ksMock) c.Validate(mc, mk)
assert.Equal(t, 2, c.RefreshRate) assert.Equal(t, 2, c.RefreshRate)
assert.Equal(t, 1000, c.LogBufferSize) assert.Equal(t, 1000, c.LogBufferSize)
@ -30,15 +29,14 @@ func TestK9sValidate(t *testing.T) {
} }
func TestK9sValidateBlank(t *testing.T) { func TestK9sValidateBlank(t *testing.T) {
setup(t) mc := NewMockConnection()
mk := NewMockKubeSettings()
ksMock := NewMockKubeSettings() m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil)
m.When(ksMock.CurrentContextName()).ThenReturn("ctx1", nil) m.When(mk.CurrentClusterName()).ThenReturn("c1", nil)
m.When(ksMock.CurrentClusterName()).ThenReturn("c1", nil) m.When(mk.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil)
var c config.K9s var c config.K9s
c.Validate(ksMock) c.Validate(mc, mk)
assert.Equal(t, 2, c.RefreshRate) assert.Equal(t, 2, c.RefreshRate)
assert.Equal(t, 1000, c.LogBufferSize) assert.Equal(t, 1000, c.LogBufferSize)
@ -51,8 +49,6 @@ func TestK9sValidateBlank(t *testing.T) {
} }
func TestK9sActiveClusterZero(t *testing.T) { func TestK9sActiveClusterZero(t *testing.T) {
setup(t)
c := config.NewK9s() c := config.NewK9s()
c.CurrentCluster = "fred" c.CurrentCluster = "fred"
cl := c.ActiveCluster() cl := c.ActiveCluster()
@ -62,18 +58,14 @@ func TestK9sActiveClusterZero(t *testing.T) {
} }
func TestK9sActiveClusterBlank(t *testing.T) { func TestK9sActiveClusterBlank(t *testing.T) {
setup(t)
var c config.K9s var c config.K9s
cl := c.ActiveCluster() cl := c.ActiveCluster()
assert.Nil(t, cl) assert.Nil(t, cl)
} }
func TestK9sActiveCluster(t *testing.T) { func TestK9sActiveCluster(t *testing.T) {
setup(t) mk := NewMockKubeSettings()
cfg := config.NewConfig(mk)
ksMock := NewMockKubeSettings()
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s.yml")) assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cl := cfg.K9s.ActiveCluster() cl := cfg.K9s.ActiveCluster()

View File

@ -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() {
}

View File

@ -5,6 +5,7 @@ package config_test
import ( import (
pegomock "github.com/petergtz/pegomock" pegomock "github.com/petergtz/pegomock"
v1 "k8s.io/api/core/v1"
"reflect" "reflect"
"time" "time"
) )
@ -93,23 +94,19 @@ func (mock *MockKubeSettings) CurrentNamespaceName() (string, error) {
return ret0, ret1 return ret0, ret1
} }
func (mock *MockKubeSettings) NamespaceNames() ([]string, error) { func (mock *MockKubeSettings) NamespaceNames(_param0 []v1.Namespace) []string {
if mock == nil { if mock == nil {
panic("mock must not be nil. Use myMock := NewMockKubeSettings().") panic("mock must not be nil. Use myMock := NewMockKubeSettings().")
} }
params := []pegomock.Param{} params := []pegomock.Param{_param0}
result := pegomock.GetGenericMockFrom(mock).Invoke("NamespaceNames", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) result := pegomock.GetGenericMockFrom(mock).Invoke("NamespaceNames", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem()})
var ret0 []string var ret0 []string
var ret1 error
if len(result) != 0 { if len(result) != 0 {
if result[0] != nil { if result[0] != nil {
ret0 = result[0].([]string) ret0 = result[0].([]string)
} }
if result[1] != nil {
ret1 = result[1].(error)
} }
} return ret0
return ret0, ret1
} }
func (mock *MockKubeSettings) VerifyWasCalledOnce() *VerifierKubeSettings { func (mock *MockKubeSettings) VerifyWasCalledOnce() *VerifierKubeSettings {
@ -217,8 +214,8 @@ func (c *KubeSettings_CurrentNamespaceName_OngoingVerification) GetCapturedArgum
func (c *KubeSettings_CurrentNamespaceName_OngoingVerification) GetAllCapturedArguments() { func (c *KubeSettings_CurrentNamespaceName_OngoingVerification) GetAllCapturedArguments() {
} }
func (verifier *VerifierKubeSettings) NamespaceNames() *KubeSettings_NamespaceNames_OngoingVerification { func (verifier *VerifierKubeSettings) NamespaceNames(_param0 []v1.Namespace) *KubeSettings_NamespaceNames_OngoingVerification {
params := []pegomock.Param{} params := []pegomock.Param{_param0}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NamespaceNames", params, verifier.timeout) methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NamespaceNames", params, verifier.timeout)
return &KubeSettings_NamespaceNames_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} return &KubeSettings_NamespaceNames_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
} }
@ -228,8 +225,18 @@ type KubeSettings_NamespaceNames_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation 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
} }

View File

@ -1,9 +1,7 @@
package config package config
import ( import (
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
const ( const (
@ -28,13 +26,13 @@ func NewNamespace() *Namespace {
} }
// Validate a namespace is setup correctly // Validate a namespace is setup correctly
func (n *Namespace) Validate(c k8s.Connection, ks KubeSettings) { func (n *Namespace) Validate(c Connection, ks KubeSettings) {
nns, err := c.DialOrDie().CoreV1().Namespaces().List(metav1.ListOptions{}) nns, err := c.ValidNamespaces()
if err != nil { if err != nil {
return return
} }
nn := ks.NamespaceNames(nns.Items) nn := ks.NamespaceNames(nns)
if !n.isAllNamespace() && !InList(nn, n.Active) { if !n.isAllNamespace() && !InList(nn, n.Active) {
log.Debug().Msg("[Config] Validation error active namespace resetting to `default") log.Debug().Msg("[Config] Validation error active namespace resetting to `default")
n.Active = defaultNS n.Active = defaultNS

View File

@ -1,7 +1,7 @@
package config_test package config_test
import ( import (
"errors" "fmt"
"testing" "testing"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
@ -10,43 +10,43 @@ import (
) )
func TestNSValidate(t *testing.T) { 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 := config.NewNamespace()
ns.Validate(mc, mk)
ksMock := NewMockKubeSettings() mk.VerifyWasCalledOnce()
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil)
ns.Validate(ksMock)
ksMock.VerifyWasCalledOnce()
assert.Equal(t, "default", ns.Active) assert.Equal(t, "default", ns.Active)
assert.Equal(t, []string{"default"}, ns.Favorites) assert.Equal(t, []string{"default"}, ns.Favorites)
} }
func TestNSValidateMissing(t *testing.T) { 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 := config.NewNamespace()
ns.Validate(mc, mk)
ksMock := NewMockKubeSettings() mk.VerifyWasCalledOnce()
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2"}, nil)
ns.Validate(ksMock)
ksMock.VerifyWasCalledOnce()
assert.Equal(t, "default", ns.Active) assert.Equal(t, "default", ns.Active)
assert.Equal(t, []string{}, ns.Favorites) assert.Equal(t, []string{}, ns.Favorites)
} }
func TestNSValidateNoNS(t *testing.T) { 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 := config.NewNamespace()
ns.Validate(mc, mk)
ksMock := NewMockKubeSettings() mk.VerifyWasCalledOnce()
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2"}, errors.New("boom"))
ns.Validate(ksMock)
ksMock.VerifyWasCalledOnce()
assert.Equal(t, "default", ns.Active) assert.Equal(t, "default", ns.Active)
assert.Equal(t, []string{"default"}, ns.Favorites) assert.Equal(t, []string{"default"}, ns.Favorites)
} }
@ -64,12 +64,13 @@ func TestNSSetActive(t *testing.T) {
{"ns4", allNS}, {"ns4", allNS},
} }
ksMock := NewMockKubeSettings() mk := NewMockKubeSettings()
m.When(ksMock.NamespaceNames()).ThenReturn(allNS, nil) m.When(mk.NamespaceNames(namespaces())).ThenReturn(allNS)
ns := config.NewNamespace() ns := config.NewNamespace()
for _, u := range uu { for _, u := range uu {
err := ns.SetActive(u.ns, ksMock) err := ns.SetActive(u.ns, mk)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, u.ns, ns.Active) assert.Equal(t, u.ns, ns.Active)
assert.Equal(t, u.fav, ns.Favorites) assert.Equal(t, u.fav, ns.Favorites)
@ -79,12 +80,13 @@ func TestNSSetActive(t *testing.T) {
func TestNSValidateRmFavs(t *testing.T) { func TestNSValidateRmFavs(t *testing.T) {
allNS := []string{"default", "kube-system"} allNS := []string{"default", "kube-system"}
ksMock := NewMockKubeSettings() mc := NewMockConnection()
m.When(ksMock.NamespaceNames()).ThenReturn(allNS, nil) mk := NewMockKubeSettings()
m.When(mk.NamespaceNames(namespaces())).ThenReturn(allNS)
ns := config.NewNamespace() ns := config.NewNamespace()
ns.Favorites = []string{"default", "fred", "blee"} ns.Favorites = []string{"default", "fred", "blee"}
ns.Validate(mc, mk)
ns.Validate(ksMock) assert.Equal(t, []string{"fred"}, ns.Favorites)
assert.Equal(t, []string{"default"}, ns.Favorites)
} }

View File

@ -3,6 +3,8 @@ package k8s
import ( import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "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/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
@ -47,6 +49,7 @@ type (
HasMetrics() bool HasMetrics() bool
IsNamespaced(n string) bool IsNamespaced(n string) bool
SupportsResource(group string) bool SupportsResource(group string) bool
ValidNamespaces() ([]v1.Namespace, error)
} }
// APIClient represents a Kubernetes api client. // APIClient represents a Kubernetes api client.
@ -70,6 +73,16 @@ func InitConnectionOrDie(config *Config, logger zerolog.Logger) *APIClient {
return &conn 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 // IsNamespaced check on server if given resource is namespaced
func (a *APIClient) IsNamespaced(res string) bool { func (a *APIClient) IsNamespaced(res string) bool {
list, _ := a.DialOrDie().Discovery().ServerPreferredResources() list, _ := a.DialOrDie().Discovery().ServerPreferredResources()

View File

@ -2,6 +2,8 @@ package k8s
import ( import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// Cluster represents a Kubernetes cluster. // Cluster represents a Kubernetes cluster.
@ -55,3 +57,13 @@ func (c *Cluster) UserName() string {
} }
return usr 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
}

View File

@ -7,17 +7,6 @@ import (
"k8s.io/client-go/tools/clientcmd/api" "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. // NamedContext represents a named cluster context.
type NamedContext struct { type NamedContext struct {
Name string Name string
@ -39,6 +28,8 @@ func (c *NamedContext) MustCurrentContextName() string {
return cl return cl
} }
// ----------------------------------------------------------------------------
// Context represents a Kubernetes Context. // Context represents a Kubernetes Context.
type Context struct { type Context struct {
Connection Connection
@ -84,6 +75,15 @@ func (c *Context) Delete(_, n string) error {
return c.Config().DelContext(n) 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. // Switch to another context.
func (c *Context) Switch(ctx string) error { func (c *Context) Switch(ctx string) error {
c.SwitchContextOrDie(ctx) c.SwitchContextOrDie(ctx)

View File

@ -10,12 +10,8 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers" "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" "k8s.io/kubernetes/pkg/kubectl/describe"
versioned "k8s.io/kubernetes/pkg/kubectl/describe/versioned" versioned "k8s.io/kubernetes/pkg/kubectl/describe/versioned"
v "k8s.io/metrics/pkg/client/clientset/versioned"
) )
type ( type (
@ -27,18 +23,7 @@ type (
} }
// Connection represents a Kubenetes apiserver connection. // Connection represents a Kubenetes apiserver connection.
Connection interface { Connection k8s.Connection
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
}
// Factory creates new tabular resources. // Factory creates new tabular resources.
Factory interface { Factory interface {

View File

@ -6,10 +6,16 @@ import (
) )
type ( type (
// SwitchableResource represents a resource that can be switched. // Switchable represents a switchable resource.
SwitchableResource interface { Switchable interface {
Switch(ctx string) error
MustCurrentContextName() string
}
// SwitchableCruder represents a resource that can be switched.
SwitchableCruder interface {
Cruder Cruder
k8s.Switchable Switchable
} }
// Context tracks a kubernetes resource. // Context tracks a kubernetes resource.
@ -50,7 +56,7 @@ func (r *Context) New(i interface{}) Columnar {
// Switch out current context. // Switch out current context.
func (r *Context) Switch(c string) error { func (r *Context) Switch(c string) error {
return r.Resource.(SwitchableResource).Switch(c) return r.Resource.(Switchable).Switch(c)
} }
// Marshal the resource to yaml. // Marshal the resource to yaml.

View File

@ -15,7 +15,7 @@ func NewContextListWithArgs(ns string, ctx *resource.Context) resource.List {
return resource.NewList(resource.NotNamespaced, "ctx", ctx, resource.SwitchAccess) 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 := &resource.Context{Base: resource.NewBase(c, s)}
ctx.Factory = ctx ctx.Factory = ctx
return ctx return ctx

View File

@ -13,7 +13,7 @@ type ClusterRole struct {
} }
// NewClusterRoleList returns a new resource list. // NewClusterRoleList returns a new resource list.
func NewClusterRoleList(c k8s.Connection, ns string) List { func NewClusterRoleList(c Connection, ns string) List {
return NewList( return NewList(
NotNamespaced, NotNamespaced,
"clusterrole", "clusterrole",
@ -23,7 +23,7 @@ func NewClusterRoleList(c k8s.Connection, ns string) List {
} }
// NewClusterRole instantiates a new ClusterRole. // 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 := &ClusterRole{&Base{Connection: c, Resource: k8s.NewClusterRole(c)}, nil}
cr.Factory = cr cr.Factory = cr

View File

@ -13,7 +13,7 @@ type ClusterRoleBinding struct {
} }
// NewClusterRoleBindingList returns a new resource list. // NewClusterRoleBindingList returns a new resource list.
func NewClusterRoleBindingList(c k8s.Connection, _ string) List { func NewClusterRoleBindingList(c Connection, _ string) List {
return NewList( return NewList(
NotNamespaced, NotNamespaced,
"clusterrolebinding", "clusterrolebinding",
@ -23,7 +23,7 @@ func NewClusterRoleBindingList(c k8s.Connection, _ string) List {
} }
// NewClusterRoleBinding instantiates a new ClusterRoleBinding. // 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 := &ClusterRoleBinding{&Base{Connection: c, Resource: k8s.NewClusterRoleBinding(c)}, nil}
crb.Factory = crb crb.Factory = crb

View File

@ -17,7 +17,7 @@ type CRD struct {
} }
// NewCRDList returns a new resource list. // NewCRDList returns a new resource list.
func NewCRDList(c k8s.Connection, ns string) List { func NewCRDList(c Connection, ns string) List {
return NewList( return NewList(
NotNamespaced, NotNamespaced,
"crd", "crd",
@ -27,7 +27,7 @@ func NewCRDList(c k8s.Connection, ns string) List {
} }
// NewCRD instantiates a new CRD. // 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 := &CRD{&Base{Connection: c, Resource: k8s.NewCRD(c)}, nil}
crd.Factory = crd crd.Factory = crd

View File

@ -28,7 +28,7 @@ type (
) )
// NewCronJobList returns a new resource list. // NewCronJobList returns a new resource list.
func NewCronJobList(c k8s.Connection, ns string) List { func NewCronJobList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"cronjob", "cronjob",
@ -38,7 +38,7 @@ func NewCronJobList(c k8s.Connection, ns string) List {
} }
// NewCronJob instantiates a new CronJob. // 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 := &CronJob{&Base{Connection: c, Resource: k8s.NewCronJob(c)}, nil}
cj.Factory = cj cj.Factory = cj

View File

@ -12,7 +12,7 @@ import (
) )
func NewCronJobListWithArgs(ns string, r *resource.CronJob) resource.List { 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 { func NewCronJobWithArgs(conn k8s.Connection, res resource.Cruder) *resource.CronJob {
@ -31,7 +31,7 @@ func TestCronJobListAccess(t *testing.T) {
l.SetNamespace(ns) l.SetNamespace(ns)
assert.Equal(t, ns, l.GetNamespace()) 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} { for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
assert.True(t, l.Access(a)) assert.True(t, l.Access(a))
} }

View File

@ -15,7 +15,7 @@ type Deployment struct {
} }
// NewDeploymentList returns a new resource list. // NewDeploymentList returns a new resource list.
func NewDeploymentList(c k8s.Connection, ns string) List { func NewDeploymentList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"deploy", "deploy",
@ -25,7 +25,7 @@ func NewDeploymentList(c k8s.Connection, ns string) List {
} }
// NewDeployment instantiates a new Deployment. // 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 := &Deployment{&Base{Connection: c, Resource: k8s.NewDeployment(c)}, nil}
d.Factory = d d.Factory = d

View File

@ -15,7 +15,7 @@ type DaemonSet struct {
} }
// NewDaemonSetList returns a new resource list. // NewDaemonSetList returns a new resource list.
func NewDaemonSetList(c k8s.Connection, ns string) List { func NewDaemonSetList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"ds", "ds",
@ -25,7 +25,7 @@ func NewDaemonSetList(c k8s.Connection, ns string) List {
} }
// NewDaemonSet instantiates a new DaemonSet. // 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 := &DaemonSet{&Base{Connection: c, Resource: k8s.NewDaemonSet(c)}, nil}
ds.Factory = ds ds.Factory = ds

View File

@ -17,7 +17,7 @@ type Endpoints struct {
} }
// NewEndpointsList returns a new resource list. // NewEndpointsList returns a new resource list.
func NewEndpointsList(c k8s.Connection, ns string) List { func NewEndpointsList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"ep", "ep",
@ -27,7 +27,7 @@ func NewEndpointsList(c k8s.Connection, ns string) List {
} }
// NewEndpoints instantiates a new Endpoints. // 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 := &Endpoints{&Base{Connection: c, Resource: k8s.NewEndpoints(c)}, nil}
ep.Factory = ep ep.Factory = ep

View File

@ -16,7 +16,7 @@ type Event struct {
} }
// NewEventList returns a new resource list. // NewEventList returns a new resource list.
func NewEventList(c k8s.Connection, ns string) List { func NewEventList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"ev", "ev",
@ -26,7 +26,7 @@ func NewEventList(c k8s.Connection, ns string) List {
} }
// NewEvent instantiates a new Event. // 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 := &Event{&Base{Connection: c, Resource: k8s.NewEvent(c)}, nil}
ev.Factory = ev ev.Factory = ev

View File

@ -17,7 +17,7 @@ type HPA struct {
} }
// NewHPAList returns a new resource list. // NewHPAList returns a new resource list.
func NewHPAList(c k8s.Connection, ns string) List { func NewHPAList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"hpa", "hpa",
@ -27,7 +27,7 @@ func NewHPAList(c k8s.Connection, ns string) List {
} }
// NewHPA instantiates a new HPA. // 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 := &HPA{&Base{Connection: c, Resource: k8s.NewHPA(c)}, nil}
hpa.Factory = hpa hpa.Factory = hpa

View File

@ -16,7 +16,7 @@ type Ingress struct {
} }
// NewIngressList returns a new resource list. // NewIngressList returns a new resource list.
func NewIngressList(c k8s.Connection, ns string) List { func NewIngressList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"ing", "ing",
@ -26,7 +26,7 @@ func NewIngressList(c k8s.Connection, ns string) List {
} }
// NewIngress instantiates a new Ingress. // 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 := &Ingress{&Base{Connection: c, Resource: k8s.NewIngress(c)}, nil}
ing.Factory = ing ing.Factory = ing

View File

@ -4,22 +4,24 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log" "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" "k8s.io/apimachinery/pkg/util/duration"
) )
// Job tracks a kubernetes resource. // Job tracks a kubernetes resource.
type Job struct { type Job struct {
*Base *Base
instance *v1.Job instance *batchv1.Job
} }
// NewJobList returns a new resource list. // NewJobList returns a new resource list.
func NewJobList(c k8s.Connection, ns string) List { func NewJobList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"job", "job",
@ -29,7 +31,7 @@ func NewJobList(c k8s.Connection, ns string) List {
} }
// NewJob instantiates a new Job. // 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 := &Job{&Base{Connection: c, Resource: k8s.NewJob(c)}, nil}
j.Factory = j j.Factory = j
@ -40,9 +42,9 @@ func NewJob(c k8s.Connection) *Job {
func (r *Job) New(i interface{}) Columnar { func (r *Job) New(i interface{}) Columnar {
c := NewJob(r.Connection) c := NewJob(r.Connection)
switch instance := i.(type) { switch instance := i.(type) {
case *v1.Job: case *batchv1.Job:
c.instance = instance c.instance = instance
case v1.Job: case batchv1.Job:
c.instance = &instance c.instance = &instance
default: default:
log.Fatal().Msgf("unknown Job type %#v", i) log.Fatal().Msgf("unknown Job type %#v", i)
@ -60,7 +62,7 @@ func (r *Job) Marshal(path string) (string, error) {
return "", err return "", err
} }
jo := i.(*v1.Job) jo := i.(*batchv1.Job)
jo.TypeMeta.APIVersion = "extensions/v1beta1" jo.TypeMeta.APIVersion = "extensions/v1beta1"
jo.TypeMeta.Kind = "Job" jo.TypeMeta.Kind = "Job"
@ -121,7 +123,7 @@ func (*Job) Header(ns string) Row {
hh = append(hh, "NAMESPACE") hh = append(hh, "NAMESPACE")
} }
return append(hh, "NAME", "COMPLETIONS", "DURATION", "AGE") return append(hh, "NAME", "COMPLETIONS", "DURATION", "CONTAINERS", "IMAGES", "AGE")
} }
// Fields retrieves displayable fields. // Fields retrieves displayable fields.
@ -133,10 +135,14 @@ func (r *Job) Fields(ns string) Row {
ff = append(ff, i.Namespace) ff = append(ff, i.Namespace)
} }
cc, ii := r.toContainers(i.Spec.Template.Spec)
return append(ff, return append(ff,
i.Name, i.Name,
r.toCompletion(i.Spec, i.Status), r.toCompletion(i.Spec, i.Status),
r.toDuration(i.Status), r.toDuration(i.Status),
cc,
ii,
toAge(i.ObjectMeta.CreationTimestamp), toAge(i.ObjectMeta.CreationTimestamp),
) )
} }
@ -144,7 +150,30 @@ func (r *Job) Fields(ns string) Row {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Helpers... // 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 { if spec.Completions != nil {
return fmt.Sprintf("%d/%d", status.Succeeded, *spec.Completions) 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) return fmt.Sprintf("%d/1", status.Succeeded)
} }
func (*Job) toDuration(status v1.JobStatus) string { func (*Job) toDuration(status batchv1.JobStatus) string {
switch { switch {
case status.StartTime == nil: case status.StartTime == nil:
case status.CompletionTime == nil: case status.CompletionTime == nil:

View File

@ -70,7 +70,7 @@ func TestJobListData(t *testing.T) {
assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, 1, len(td.Rows))
assert.Equal(t, "blee", l.GetNamespace()) assert.Equal(t, "blee", l.GetNamespace())
row := td.Rows["blee/fred"] row := td.Rows["blee/fred"]
assert.Equal(t, 4, len(row.Deltas)) assert.Equal(t, 6, len(row.Deltas))
for _, d := range row.Deltas { for _, d := range row.Deltas {
assert.Equal(t, "", d) assert.Equal(t, "", d)
} }

View File

@ -234,6 +234,25 @@ func (mock *MockClusterMeta) UserName() string {
return ret0 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) { func (mock *MockClusterMeta) Version() (string, error) {
if mock == nil { if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterMeta().") 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 (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 { func (verifier *VerifierClusterMeta) Version() *ClusterMeta_Version_OngoingVerification {
params := []pegomock.Param{} params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Version", params, verifier.timeout) methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Version", params, verifier.timeout)

View File

@ -6,6 +6,7 @@ package resource_test
import ( import (
k8s "github.com/derailed/k9s/internal/k8s" k8s "github.com/derailed/k9s/internal/k8s"
pegomock "github.com/petergtz/pegomock" pegomock "github.com/petergtz/pegomock"
v1 "k8s.io/api/core/v1"
dynamic "k8s.io/client-go/dynamic" dynamic "k8s.io/client-go/dynamic"
kubernetes "k8s.io/client-go/kubernetes" kubernetes "k8s.io/client-go/kubernetes"
rest "k8s.io/client-go/rest" rest "k8s.io/client-go/rest"
@ -169,6 +170,25 @@ func (mock *MockConnection) SwitchContextOrDie(_param0 string) {
pegomock.GetGenericMockFrom(mock).Invoke("SwitchContextOrDie", params, []reflect.Type{}) 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 { func (mock *MockConnection) VerifyWasCalledOnce() *VerifierConnection {
return &VerifierConnection{ return &VerifierConnection{
mock: mock, mock: mock,
@ -405,3 +425,20 @@ func (c *Connection_SwitchContextOrDie_OngoingVerification) GetAllCapturedArgume
} }
return 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() {
}

View File

@ -24,7 +24,7 @@ type Node struct {
} }
// NewNodeList returns a new resource list. // 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( return NewList(
NotNamespaced, NotNamespaced,
"no", "no",
@ -34,7 +34,7 @@ func NewNodeList(c k8s.Connection, mx MetricsServer, ns string) List {
} }
// NewNode instantiates a new Node. // 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 := &Node{&Base{Connection: c, Resource: k8s.NewNode(c)}, nil, mx, k8s.NodeMetrics{}}
n.Factory = n n.Factory = n

View File

@ -13,7 +13,7 @@ type Namespace struct {
} }
// NewNamespaceList returns a new resource list. // NewNamespaceList returns a new resource list.
func NewNamespaceList(c k8s.Connection, ns string) List { func NewNamespaceList(c Connection, ns string) List {
return NewList( return NewList(
NotNamespaced, NotNamespaced,
"ns", "ns",
@ -23,7 +23,7 @@ func NewNamespaceList(c k8s.Connection, ns string) List {
} }
// NewNamespace instantiates a new Namespace. // 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 := &Namespace{&Base{Connection: c, Resource: k8s.NewNamespace(c)}, nil}
n.Factory = n n.Factory = n

View File

@ -16,7 +16,7 @@ type PodDisruptionBudget struct {
} }
// NewPDBList returns a new resource list. // NewPDBList returns a new resource list.
func NewPDBList(c k8s.Connection, ns string) List { func NewPDBList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"pdb", "pdb",
@ -26,7 +26,7 @@ func NewPDBList(c k8s.Connection, ns string) List {
} }
// NewPDB instantiates a new PDB. // 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 := &PodDisruptionBudget{&Base{Connection: c, Resource: k8s.NewPodDisruptionBudget(c)}, nil}
p.Factory = p p.Factory = p

View File

@ -44,7 +44,7 @@ type (
) )
// NewPodList returns a new resource list. // 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( return NewList(
ns, ns,
"po", "po",
@ -54,7 +54,7 @@ func NewPodList(c k8s.Connection, mx MetricsServer, ns string) List {
} }
// NewPod instantiates a new Pod. // 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 := &Pod{&Base{Connection: c, Resource: k8s.NewPod(c)}, nil, mx, k8s.PodMetrics{}}
p.Factory = p p.Factory = p

View File

@ -16,7 +16,7 @@ type PV struct {
} }
// NewPVList returns a new resource list. // NewPVList returns a new resource list.
func NewPVList(c k8s.Connection, ns string) List { func NewPVList(c Connection, ns string) List {
return NewList( return NewList(
NotNamespaced, NotNamespaced,
"pv", "pv",
@ -26,7 +26,7 @@ func NewPVList(c k8s.Connection, ns string) List {
} }
// NewPV instantiates a new PV. // 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 := &PV{&Base{Connection: c, Resource: k8s.NewPV(c)}, nil}
p.Factory = p p.Factory = p

View File

@ -13,7 +13,7 @@ type PVC struct {
} }
// NewPVCList returns a new resource list. // NewPVCList returns a new resource list.
func NewPVCList(c k8s.Connection, ns string) List { func NewPVCList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"pvc", "pvc",
@ -23,7 +23,7 @@ func NewPVCList(c k8s.Connection, ns string) List {
} }
// NewPVC instantiates a new PVC. // 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 := &PVC{&Base{Connection: c, Resource: k8s.NewPVC(c)}, nil}
p.Factory = p p.Factory = p

View File

@ -15,7 +15,7 @@ type ReplicationController struct {
} }
// NewReplicationControllerList returns a new resource list. // NewReplicationControllerList returns a new resource list.
func NewReplicationControllerList(c k8s.Connection, ns string) List { func NewReplicationControllerList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"rc", "rc",
@ -25,7 +25,7 @@ func NewReplicationControllerList(c k8s.Connection, ns string) List {
} }
// NewReplicationController instantiates a new ReplicationController. // 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 := &ReplicationController{&Base{Connection: c, Resource: k8s.NewReplicationController(c)}, nil}
r.Factory = r r.Factory = r

View File

@ -15,7 +15,7 @@ type Role struct {
} }
// NewRoleList returns a new resource list. // NewRoleList returns a new resource list.
func NewRoleList(c k8s.Connection, ns string) List { func NewRoleList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"role", "role",
@ -25,7 +25,7 @@ func NewRoleList(c k8s.Connection, ns string) List {
} }
// NewRole instantiates a new Role. // 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 := &Role{&Base{Connection: c, Resource: k8s.NewRole(c)}, nil}
r.Factory = r r.Factory = r

View File

@ -15,7 +15,7 @@ type RoleBinding struct {
} }
// NewRoleBindingList returns a new resource list. // NewRoleBindingList returns a new resource list.
func NewRoleBindingList(c k8s.Connection, ns string) List { func NewRoleBindingList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"rolebinding", "rolebinding",
@ -25,7 +25,7 @@ func NewRoleBindingList(c k8s.Connection, ns string) List {
} }
// NewRoleBinding instantiates a new RoleBinding. // 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 := &RoleBinding{&Base{Connection: c, Resource: k8s.NewRoleBinding(c)}, nil}
r.Factory = r r.Factory = r

View File

@ -15,7 +15,7 @@ type ReplicaSet struct {
} }
// NewReplicaSetList returns a new resource list. // NewReplicaSetList returns a new resource list.
func NewReplicaSetList(c k8s.Connection, ns string) List { func NewReplicaSetList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"rs", "rs",
@ -25,7 +25,7 @@ func NewReplicaSetList(c k8s.Connection, ns string) List {
} }
// NewReplicaSet instantiates a new ReplicaSet. // 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 := &ReplicaSet{&Base{Connection: c, Resource: k8s.NewReplicaSet(c)}, nil}
r.Factory = r r.Factory = r

View File

@ -15,7 +15,7 @@ type ServiceAccount struct {
} }
// NewServiceAccountList returns a new resource list. // NewServiceAccountList returns a new resource list.
func NewServiceAccountList(c k8s.Connection, ns string) List { func NewServiceAccountList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"sa", "sa",
@ -25,7 +25,7 @@ func NewServiceAccountList(c k8s.Connection, ns string) List {
} }
// NewServiceAccount instantiates a new ServiceAccount. // 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 := &ServiceAccount{&Base{Connection: c, Resource: k8s.NewServiceAccount(c)}, nil}
s.Factory = s s.Factory = s

View File

@ -15,7 +15,7 @@ type Secret struct {
} }
// NewSecretList returns a new resource list. // NewSecretList returns a new resource list.
func NewSecretList(c k8s.Connection, ns string) List { func NewSecretList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"secret", "secret",
@ -25,7 +25,7 @@ func NewSecretList(c k8s.Connection, ns string) List {
} }
// NewSecret instantiates a new Secret. // 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 := &Secret{&Base{Connection: c, Resource: k8s.NewSecret(c)}, nil}
s.Factory = s s.Factory = s

View File

@ -15,7 +15,7 @@ type StatefulSet struct {
} }
// NewStatefulSetList returns a new resource list. // NewStatefulSetList returns a new resource list.
func NewStatefulSetList(c k8s.Connection, ns string) List { func NewStatefulSetList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"sts", "sts",
@ -25,7 +25,7 @@ func NewStatefulSetList(c k8s.Connection, ns string) List {
} }
// NewStatefulSet instantiates a new StatefulSet. // 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 := &StatefulSet{&Base{Connection: c, Resource: k8s.NewStatefulSet(c)}, nil}
s.Factory = s s.Factory = s

View File

@ -19,7 +19,7 @@ type Service struct {
} }
// NewServiceList returns a new resource list. // NewServiceList returns a new resource list.
func NewServiceList(c k8s.Connection, ns string) List { func NewServiceList(c Connection, ns string) List {
return NewList( return NewList(
ns, ns,
"svc", "svc",
@ -29,7 +29,7 @@ func NewServiceList(c k8s.Connection, ns string) List {
} }
// NewService instantiates a new Service. // 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 := &Service{&Base{Connection: c, Resource: k8s.NewService(c)}, nil}
s.Factory = s s.Factory = s

View File

@ -128,12 +128,6 @@ func (a *appView) conn() k8s.Connection {
// Run starts the application loop // Run starts the application loop
func (a *appView) Run() { func (a *appView) Run() {
defer func() {
if err := recover(); err != nil {
log.Error().Msgf("%#v", err)
}
}()
go func() { go func() {
<-time.After(splashTime * time.Second) <-time.After(splashTime * time.Second)
a.showPage("main") a.showPage("main")
@ -302,9 +296,9 @@ func logoView() tview.Primitive {
v.SetWordWrap(false) v.SetWordWrap(false)
v.SetWrap(false) v.SetWrap(false)
v.SetDynamicColors(true) v.SetDynamicColors(true)
for i, s := range logoSmall { for i, s := range LogoSmall {
fmt.Fprintf(v, "[orange::b]%s", s) fmt.Fprintf(v, "[orange::b]%s", s)
if i+1 < len(logoSmall) { if i+1 < len(LogoSmall) {
fmt.Fprintf(v, "\n") fmt.Fprintf(v, "\n")
} }
} }

View File

@ -1,6 +1,7 @@
package views package views
import ( import (
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
@ -18,7 +19,7 @@ func newInfoView(app *appView) *clusterInfoView {
return &clusterInfoView{ return &clusterInfoView{
app: app, app: app,
Table: tview.NewTable(), 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()) v.GetCell(row, 1).SetText(v.cluster.Version())
row++ row++
nodes, err := v.cluster.GetNodes() nodes, err := v.cluster.FetchNodes()
if err != nil { if err != nil {
log.Warn().Msgf("ClusterInfo %s", err) log.Warn().Msgf("ClusterInfo %s", err)
return return
} }
mxNodes, err := v.cluster.GetNodesMetrics() mxNodes, err := v.cluster.FetchNodesMetrics()
if err != nil { if err != nil {
log.Warn().Msgf("ClusterInfo %s", err) log.Warn().Msgf("ClusterInfo %s", err)
return return

View File

@ -3,6 +3,7 @@ package views
import ( import (
"fmt" "fmt"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -53,8 +54,15 @@ func (c *command) run(cmd string) bool {
return true return true
default: default:
if res, ok := resourceViews()[cmd]; ok { 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())) 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) c.exec(cmd, v)
return true return true
} }

View File

@ -8,7 +8,8 @@ import (
type ( type (
viewFn func(ns string, app *appView, list resource.List, colorer colorerFn) resourceViewer 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 colorerFn func(ns string, evt *resource.RowEvent) tcell.Color
resCmd struct { resCmd struct {
@ -16,6 +17,7 @@ type (
api string api string
viewFn viewFn viewFn viewFn
listFn listFn listFn listFn
listMxFn listMxFn
colorerFn colorerFn colorerFn colorerFn
} }
) )
@ -100,7 +102,7 @@ func resourceViews() map[string]resCmd {
listFn: resource.NewCRDList, listFn: resource.NewCRDList,
colorerFn: defaultColorer, colorerFn: defaultColorer,
}, },
"cron": { "cj": {
title: "CronJobs", title: "CronJobs",
api: "batch", api: "batch",
viewFn: newCronJobView, viewFn: newCronJobView,
@ -156,7 +158,7 @@ func resourceViews() map[string]resCmd {
listFn: resource.NewIngressList, listFn: resource.NewIngressList,
colorerFn: defaultColorer, colorerFn: defaultColorer,
}, },
"job": { "jo": {
title: "Jobs", title: "Jobs",
api: "batch", api: "batch",
viewFn: newJobView, viewFn: newJobView,
@ -167,7 +169,7 @@ func resourceViews() map[string]resCmd {
title: "Nodes", title: "Nodes",
api: "", api: "",
viewFn: newNodeView, viewFn: newNodeView,
listFn: resource.NewNodeList, listMxFn: resource.NewNodeList,
colorerFn: nsColorer, colorerFn: nsColorer,
}, },
"ns": { "ns": {
@ -188,7 +190,7 @@ func resourceViews() map[string]resCmd {
title: "Pods", title: "Pods",
api: "", api: "",
viewFn: newPodView, viewFn: newPodView,
listFn: resource.NewPodList, listMxFn: resource.NewPodList,
colorerFn: podColorer, colorerFn: podColorer,
}, },
"pv": { "pv": {

View File

@ -80,19 +80,17 @@ func (v *resourceView) init(ctx context.Context, ns string) {
v.selectedItem, v.selectedNS = noSelection, ns v.selectedItem, v.selectedNS = noSelection, ns
go func(ctx context.Context) { go func(ctx context.Context) {
initTick := refreshDelay
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
log.Debug().Msgf("%s watcher canceled!", v.title) log.Debug().Msgf("%s watcher canceled!", v.title)
return return
case <-time.After(time.Duration(initTick) * time.Second): case <-time.After(time.Duration(v.app.config.K9s.RefreshRate) * time.Second):
v.refresh() v.refresh()
initTick = float64(v.app.config.K9s.RefreshRate)
} }
} }
}(ctx) }(ctx)
v.refreshActions() v.refresh()
if tv, ok := v.CurrentPage().Item.(*tableView); ok { if tv, ok := v.CurrentPage().Item.(*tableView); ok {
tv.Select(0, 0) tv.Select(0, 0)
} }

View File

@ -13,7 +13,7 @@ const (
product = "Kubernetes CLI Island Style!" 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) { 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) fmt.Fprintf(t, "%s[orange::b]%s\n", strings.Repeat("\n", 2), logo)
} }