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
test: ## Run all tests
@go test ./...
cover: ## Run test coverage suite
@go test ./... --coverprofile=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/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))
}

View File

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

View File

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

View File

@ -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",
},
},
}
}

View File

@ -26,8 +26,12 @@ var (
K9sLogs = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", mustK9sUser()))
)
type (
// Connection present a kubernetes api server connection.
Connection k8s.Connection
// KubeSettings exposes kubeconfig context informations.
type KubeSettings interface {
KubeSettings interface {
CurrentContextName() (string, error)
CurrentClusterName() (string, error)
CurrentNamespaceName() (string, error)
@ -36,11 +40,12 @@ type KubeSettings interface {
}
// Config tracks K9s configuration options.
type Config struct {
Config struct {
K9s *K9s `yaml:"k9s"`
client k8s.Connection
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
}

View File

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

View File

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

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 (
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
}

View File

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

View File

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

View File

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

View File

@ -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
}

View File

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

View File

@ -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 {

View File

@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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")
}
}

View File

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

View File

@ -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
}

View File

@ -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": {

View File

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

View File

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