release v0.25.11 (#1377)

mine
Fernand Galiana 2021-12-14 10:44:24 -07:00 committed by GitHub
parent e64dcbcbdc
commit 11c2ae2622
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 293 additions and 317 deletions

View File

@ -5,7 +5,7 @@ PACKAGE := github.com/derailed/$(NAME)
GIT_REV ?= $(shell git rev-parse --short HEAD) GIT_REV ?= $(shell git rev-parse --short HEAD)
SOURCE_DATE_EPOCH ?= $(shell date +%s) SOURCE_DATE_EPOCH ?= $(shell date +%s)
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
VERSION ?= v0.25.10 VERSION ?= v0.25.11
IMG_NAME := derailed/k9s IMG_NAME := derailed/k9s
IMAGE := ${IMG_NAME}:${VERSION} IMAGE := ${IMG_NAME}:${VERSION}

View File

@ -0,0 +1,42 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.25.11
## Notes
Thank you to all that contributed with flushing out issues and enhancements for 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. Your support, kindness and awesome suggestions to make K9s better are as ever very much noted and appreciated!
If you feel K9s is helping your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
### A Word From Our Sponsors...
I want to recognize the following folks that have been kind enough to join our sponsorship program and opted to `pay it forward`!
* [Joshua Kapellen](https://github.com/joshuakapellen)
* [Qdentity](https://github.com/qdentity)
* [Maxim](https://github.com/bsod90)
* [Sönke Schau](https://github.com/xgcssch)
So if you feel K9s is helping with your productivity while administering your Kubernetes clusters, please consider pitching in as it will go a long way in ensuring a thriving environment for this repo and our k9ers community at large.
Also please take some time and give a huge shoot out to all the good folks below that have spent time plowing thru the code to help improve K9s for all of us!
Thank you!!
---
## Maintenance Release!
Hoy! end of year suck... Feeling the burn ;( Apologize for the disruptions...
---
## Resolved Issues
* [Issue #1374](https://github.com/derailed/k9s/issues/1374) --all-namespaces does not work v0.25.10
* [Issue #1376](https://github.com/derailed/k9s/issues/1376) Events not sorted correctly by dates
* [Issue #1373](https://github.com/derailed/k9s/issues/1373) change namespace not possible
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -23,9 +23,8 @@ const (
// Config tracks a kubernetes configuration. // Config tracks a kubernetes configuration.
type Config struct { type Config struct {
flags *genericclioptions.ConfigFlags flags *genericclioptions.ConfigFlags
mutex *sync.RWMutex mutex *sync.RWMutex
OverrideNS bool
} }
// NewConfig returns a new k8s config or an error if the flags are invalid. // NewConfig returns a new k8s config or an error if the flags are invalid.
@ -183,15 +182,15 @@ func (c *Config) CurrentClusterName() (string, error) {
} }
// ClusterNames fetch all kubeconfig defined clusters. // ClusterNames fetch all kubeconfig defined clusters.
func (c *Config) ClusterNames() ([]string, error) { func (c *Config) ClusterNames() (map[string]struct{}, error) {
cfg, err := c.RawConfig() cfg, err := c.RawConfig()
if err != nil { if err != nil {
return nil, err return nil, err
} }
cc := make([]string, 0, len(cfg.Clusters)) cc := make(map[string]struct{}, len(cfg.Clusters))
for name := range cfg.Clusters { for name := range cfg.Clusters {
cc = append(cc, name) cc[name] = struct{}{}
} }
return cc, nil return cc, nil

View File

@ -36,15 +36,14 @@ type (
CurrentNamespaceName() (string, error) CurrentNamespaceName() (string, error)
// ClusterNames() returns all available cluster names. // ClusterNames() returns all available cluster names.
ClusterNames() ([]string, error) ClusterNames() (map[string]struct{}, error)
} }
// Config tracks K9s configuration options. // Config tracks K9s configuration options.
Config struct { Config struct {
K9s *K9s `yaml:"k9s"` K9s *K9s `yaml:"k9s"`
client client.Connection client client.Connection
settings KubeSettings settings KubeSettings
overrideNS bool
} }
) )
@ -94,11 +93,10 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c
c.K9s.ActivateCluster() c.K9s.ActivateCluster()
var ns string var ns string
var override bool
if k9sFlags != nil && IsBoolSet(k9sFlags.AllNamespaces) { if k9sFlags != nil && IsBoolSet(k9sFlags.AllNamespaces) {
ns, override = client.NamespaceAll, true ns = client.NamespaceAll
} else if isSet(flags.Namespace) { } else if isSet(flags.Namespace) {
ns, override = *flags.Namespace, true ns = *flags.Namespace
} else if context.Namespace != "" { } else if context.Namespace != "" {
ns = context.Namespace ns = context.Namespace
} else if cl := c.K9s.ActiveCluster(); cl != nil { } else if cl := c.K9s.ActiveCluster(); cl != nil {
@ -109,7 +107,7 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c
if err := c.SetActiveNamespace(ns); err != nil { if err := c.SetActiveNamespace(ns); err != nil {
return err return err
} }
flags.Namespace, c.overrideNS = &ns, override flags.Namespace = &ns
} }
if isSet(flags.ClusterName) { if isSet(flags.ClusterName) {
@ -142,6 +140,9 @@ func (c *Config) ActiveNamespace() string {
return "default" return "default"
} }
cl := c.CurrentCluster() cl := c.CurrentCluster()
if cl != nil && cl.Namespace != nil {
return cl.Namespace.Active
}
if cl == nil { if cl == nil {
cl = NewCluster() cl = NewCluster()
c.K9s.Clusters[c.K9s.CurrentCluster] = cl c.K9s.Clusters[c.K9s.CurrentCluster] = cl
@ -154,10 +155,6 @@ func (c *Config) ActiveNamespace() string {
return ns return ns
} }
if cl.Namespace != nil {
return cl.Namespace.Active
}
return "default" return "default"
} }
@ -221,9 +218,6 @@ func (c *Config) GetConnection() client.Connection {
// SetConnection set an api server connection. // SetConnection set an api server connection.
func (c *Config) SetConnection(conn client.Connection) { func (c *Config) SetConnection(conn client.Connection) {
c.client = conn c.client = conn
if c.client != nil && c.client.Config() != nil {
c.client.Config().OverrideNS = c.overrideNS
}
} }
// Load K9s configuration from file. // Load K9s configuration from file.

View File

@ -198,7 +198,7 @@ func TestConfigSaveFile(t *testing.T) {
m.When(mk.CurrentContextName()).ThenReturn("minikube", nil) m.When(mk.CurrentContextName()).ThenReturn("minikube", nil)
m.When(mk.CurrentClusterName()).ThenReturn("minikube", nil) m.When(mk.CurrentClusterName()).ThenReturn("minikube", nil)
m.When(mk.CurrentNamespaceName()).ThenReturn("default", nil) m.When(mk.CurrentNamespaceName()).ThenReturn("default", nil)
m.When(mk.ClusterNames()).ThenReturn([]string{"minikube", "fred", "blee"}, nil) m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"minikube": {}, "fred": {}, "blee": {}}, nil)
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"}) m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
cfg := config.NewConfig(mk) cfg := config.NewConfig(mk)
@ -228,7 +228,7 @@ func TestConfigReset(t *testing.T) {
m.When(mk.CurrentContextName()).ThenReturn("blee", nil) m.When(mk.CurrentContextName()).ThenReturn("blee", nil)
m.When(mk.CurrentClusterName()).ThenReturn("blee", nil) m.When(mk.CurrentClusterName()).ThenReturn("blee", nil)
m.When(mk.CurrentNamespaceName()).ThenReturn("default", nil) m.When(mk.CurrentNamespaceName()).ThenReturn("default", nil)
m.When(mk.ClusterNames()).ThenReturn([]string{"blee"}, nil) m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"blee": {}}, nil)
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"}) m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
cfg := config.NewConfig(mk) cfg := config.NewConfig(mk)
@ -271,7 +271,7 @@ func (m *mockSettings) CurrentClusterName() (string, error) { return "", nil }
func (m *mockSettings) CurrentNamespaceName() (string, error) { func (m *mockSettings) CurrentNamespaceName() (string, error) {
return *m.flags.Namespace, nil return *m.flags.Namespace, nil
} }
func (m *mockSettings) ClusterNames() ([]string, error) { return nil, nil } func (m *mockSettings) ClusterNames() (map[string]struct{}, error) { return nil, nil }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Test Data... // Test Data...

View File

@ -194,9 +194,9 @@ func (k *K9s) validateClusters(c client.Connection, ks KubeSettings) {
if err != nil { if err != nil {
return return
} }
for key := range k.Clusters { for key, cluster := range k.Clusters {
k.Clusters[key].Validate(c, ks) cluster.Validate(c, ks)
if InList(cc, key) { if _, ok := cc[key]; ok {
continue continue
} }
if k.CurrentCluster == key { if k.CurrentCluster == key {
@ -224,8 +224,8 @@ func (k *K9s) Validate(c client.Connection, ks KubeSettings) {
} }
k.Thresholds.Validate(c, ks) k.Thresholds.Validate(c, ks)
if ctx, err := ks.CurrentContextName(); err == nil && len(k.CurrentContext) == 0 { if context, err := ks.CurrentContextName(); err == nil && len(k.CurrentContext) == 0 {
k.CurrentContext = ctx k.CurrentContext = context
k.CurrentCluster = "" k.CurrentCluster = ""
} }

View File

@ -66,7 +66,7 @@ func TestK9sValidate(t *testing.T) {
mk := NewMockKubeSettings() mk := NewMockKubeSettings()
m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil) m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil)
m.When(mk.CurrentClusterName()).ThenReturn("c1", nil) m.When(mk.CurrentClusterName()).ThenReturn("c1", nil)
m.When(mk.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil) m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"c1": {}, "c2": {}}, nil)
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"}) m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
c := config.NewK9s() c := config.NewK9s()
@ -90,7 +90,7 @@ func TestK9sValidateBlank(t *testing.T) {
mk := NewMockKubeSettings() mk := NewMockKubeSettings()
m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil) m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil)
m.When(mk.CurrentClusterName()).ThenReturn("c1", nil) m.When(mk.CurrentClusterName()).ThenReturn("c1", nil)
m.When(mk.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil) m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"c1": {}, "c2": {}}, nil)
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"}) m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
var c config.K9s var c config.K9s

View File

@ -25,17 +25,17 @@ func NewMockKubeSettings(options ...pegomock.Option) *MockKubeSettings {
func (mock *MockKubeSettings) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh } func (mock *MockKubeSettings) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh }
func (mock *MockKubeSettings) FailHandler() pegomock.FailHandler { return mock.fail } func (mock *MockKubeSettings) FailHandler() pegomock.FailHandler { return mock.fail }
func (mock *MockKubeSettings) ClusterNames() ([]string, error) { func (mock *MockKubeSettings) ClusterNames() (map[string]struct{}, error) {
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{}
result := pegomock.GetGenericMockFrom(mock).Invoke("ClusterNames", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) result := pegomock.GetGenericMockFrom(mock).Invoke("ClusterNames", params, []reflect.Type{reflect.TypeOf((*map[string]struct{})(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 []string var ret0 map[string]struct{}
var ret1 error 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].(map[string]struct{})
} }
if result[1] != nil { if result[1] != nil {
ret1 = result[1].(error) ret1 = result[1].(error)

View File

@ -110,6 +110,10 @@ var Registry = map[string]ResourceMeta{
"v1/persistentvolumeclaims": { "v1/persistentvolumeclaims": {
Renderer: &render.PersistentVolumeClaim{}, Renderer: &render.PersistentVolumeClaim{},
}, },
"v1/events": {
DAO: &dao.Table{},
Renderer: &render.Event{},
},
// Apps... // Apps...
"apps/v1/deployments": { "apps/v1/deployments": {

View File

@ -240,7 +240,7 @@ func (t *Table) reconcile(ctx context.Context) error {
var rows render.Rows var rows render.Rows
if len(oo) > 0 { if len(oo) > 0 {
if _, ok := meta.Renderer.(*render.Generic); ok { if meta.Renderer.IsGeneric() {
table, ok := oo[0].(*metav1beta1.Table) table, ok := oo[0].(*metav1beta1.Table)
if !ok { if !ok {
return fmt.Errorf("expecting a meta table but got %T", oo[0]) return fmt.Errorf("expecting a meta table but got %T", oo[0])
@ -300,8 +300,13 @@ func hydrate(ns string, oo []runtime.Object, rr render.Rows, re Renderer) error
return nil return nil
} }
type Generic interface {
SetTable(*metav1beta1.Table)
Render(interface{}, string, *render.Row) error
}
func genericHydrate(ns string, table *metav1beta1.Table, rr render.Rows, re Renderer) error { func genericHydrate(ns string, table *metav1beta1.Table, rr render.Rows, re Renderer) error {
gr, ok := re.(*render.Generic) gr, ok := re.(Generic)
if !ok { if !ok {
return fmt.Errorf("expecting generic renderer but got %T", re) return fmt.Errorf("expecting generic renderer but got %T", re)
} }

View File

@ -84,6 +84,9 @@ type Component interface {
// Renderer represents a resource renderer. // Renderer represents a resource renderer.
type Renderer interface { type Renderer interface {
// IsGeneric identifies a generic handler.
IsGeneric() bool
// Render converts raw resources to tabular data. // Render converts raw resources to tabular data.
Render(o interface{}, ns string, row *render.Row) error Render(o interface{}, ns string, row *render.Row) error

View File

@ -10,11 +10,8 @@ import (
) )
// Alias renders a aliases to screen. // Alias renders a aliases to screen.
type Alias struct{} type Alias struct {
Base
// ColorerFunc colors a resource row.
func (Alias) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

26
internal/render/base.go Normal file
View File

@ -0,0 +1,26 @@
package render
// DecoratorFunc decorates a string.
type DecoratorFunc func(string) string
// AgeDecorator represents a timestamped as human column.
var AgeDecorator = func(a string) string {
return toAgeHuman(a)
}
type Base struct{}
// IsGeneric identifies a generic handler.
func (Base) IsGeneric() bool {
return false
}
// ColorerFunc colors a resource row.
func (Base) ColorerFunc() ColorerFunc {
return DefaultColorer
}
// Happy returns true if resource is happy, false otherwise.
func (Base) Happy(_ string, _ Row) bool {
return true
}

View File

@ -24,7 +24,9 @@ var (
) )
// Benchmark renders a benchmarks to screen. // Benchmark renders a benchmarks to screen.
type Benchmark struct{} type Benchmark struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (b Benchmark) ColorerFunc() ColorerFunc { func (b Benchmark) ColorerFunc() ColorerFunc {

View File

@ -35,7 +35,9 @@ type ContainerWithMetrics interface {
} }
// Container renders a K8s Container to screen. // Container renders a K8s Container to screen.
type Container struct{} type Container struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (c Container) ColorerFunc() ColorerFunc { func (c Container) ColorerFunc() ColorerFunc {

View File

@ -12,7 +12,9 @@ import (
) )
// Context renders a K8s ConfigMap to screen. // Context renders a K8s ConfigMap to screen.
type Context struct{} type Context struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (Context) ColorerFunc() ColorerFunc { func (Context) ColorerFunc() ColorerFunc {

View File

@ -10,11 +10,8 @@ import (
) )
// ClusterRole renders a K8s ClusterRole to screen. // ClusterRole renders a K8s ClusterRole to screen.
type ClusterRole struct{} type ClusterRole struct {
Base
// ColorerFunc colors a resource row.
func (ClusterRole) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header rbw. // Header returns a header rbw.

View File

@ -10,11 +10,8 @@ import (
) )
// ClusterRoleBinding renders a K8s ClusterRoleBinding to screen. // ClusterRoleBinding renders a K8s ClusterRoleBinding to screen.
type ClusterRoleBinding struct{} type ClusterRoleBinding struct {
Base
// ColorerFunc colors a resource row.
func (ClusterRoleBinding) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header rbw. // Header returns a header rbw.

View File

@ -11,11 +11,8 @@ import (
) )
// CustomResourceDefinition renders a K8s CustomResourceDefinition to screen. // CustomResourceDefinition renders a K8s CustomResourceDefinition to screen.
type CustomResourceDefinition struct{} type CustomResourceDefinition struct {
Base
// ColorerFunc colors a resource row.
func (CustomResourceDefinition) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header rbw. // Header returns a header rbw.

View File

@ -13,11 +13,8 @@ import (
) )
// CronJob renders a K8s CronJob to screen. // CronJob renders a K8s CronJob to screen.
type CronJob struct{} type CronJob struct {
Base
// ColorerFunc colors a resource row.
func (CronJob) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -12,6 +12,11 @@ import (
// Dir renders a directory entry to screen. // Dir renders a directory entry to screen.
type Dir struct{} type Dir struct{}
// IsGeneric identifies a generic handler.
func (Dir) IsGeneric() bool {
return false
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (Dir) ColorerFunc() ColorerFunc { func (Dir) ColorerFunc() ColorerFunc {
return func(ns string, _ Header, re RowEvent) tcell.Color { return func(ns string, _ Header, re RowEvent) tcell.Color {

View File

@ -12,7 +12,9 @@ import (
) )
// Deployment renders a K8s Deployment to screen. // Deployment renders a K8s Deployment to screen.
type Deployment struct{} type Deployment struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (d Deployment) ColorerFunc() ColorerFunc { func (d Deployment) ColorerFunc() ColorerFunc {

View File

@ -12,11 +12,8 @@ import (
) )
// DaemonSet renders a K8s DaemonSet to screen. // DaemonSet renders a K8s DaemonSet to screen.
type DaemonSet struct{} type DaemonSet struct {
Base
// ColorerFunc colors a resource row.
func (d DaemonSet) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -12,11 +12,8 @@ import (
) )
// Endpoints renders a K8s Endpoints to screen. // Endpoints renders a K8s Endpoints to screen.
type Endpoints struct{} type Endpoints struct {
Base
// ColorerFunc colors a resource row.
func (Endpoints) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -1,35 +1,25 @@
package render package render
import ( import (
"fmt"
"strings" "strings"
"github.com/derailed/k9s/internal/client"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
) )
// BOZO!!
// import (
// "errors"
// "fmt"
// "strconv"
// "strings"
// "time"
// "github.com/derailed/k9s/internal/client"
// "github.com/derailed/tview"
// "github.com/gdamore/tcell/v2"
// v1 "k8s.io/api/core/v1"
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
// "k8s.io/apimachinery/pkg/runtime"
// "k8s.io/apimachinery/pkg/util/duration"
// api "k8s.io/kubernetes/pkg/apis/core"
// )
// Event renders a K8s Event to screen. // Event renders a K8s Event to screen.
type Event struct{} type Event struct {
Generic
}
func (*Event) IsGeneric() bool {
return true
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (e Event) ColorerFunc() ColorerFunc { func (e *Event) ColorerFunc() ColorerFunc {
return func(ns string, h Header, re RowEvent) tcell.Color { return func(ns string, h Header, re RowEvent) tcell.Color {
if !Happy(ns, h, re.Row) { if !Happy(ns, h, re.Row) {
return ErrColor return ErrColor
@ -46,128 +36,63 @@ func (e Event) ColorerFunc() ColorerFunc {
} }
} }
// // Header returns a header rbw. var ageCols = map[string]struct{}{
// func (Event) Header(ns string) Header { "FIRST SEEN": {},
// return Header{ "LAST SEEN": {},
// HeaderColumn{Name: "NAMESPACE"}, }
// HeaderColumn{Name: "LAST SEEN"},
// HeaderColumn{Name: "TYPE"},
// HeaderColumn{Name: "REASON"},
// HeaderColumn{Name: "OBJECT"},
// HeaderColumn{Name: "SUBOBJECT"},
// HeaderColumn{Name: "SOURCE"},
// HeaderColumn{Name: "MESSAGE", Wide: true},
// HeaderColumn{Name: "FIRST SEEN", Wide: true},
// HeaderColumn{Name: "COUNT", Align: tview.AlignRight},
// HeaderColumn{Name: "NAME"},
// HeaderColumn{Name: "VALID", Wide: true},
// }
// }
// // Render renders a K8s resource to screen. var wideCols = map[string]struct{}{
// func (e Event) Render(o interface{}, ns string, r *Row) error { "SUBOBJECT": {},
// raw, ok := o.(*unstructured.Unstructured) "SOURCE": {},
// if !ok { "FIRST SEEN": {},
// return fmt.Errorf("Expected Event, but got %T", o) "NAME": {},
// } "MESSAGE": {},
// var ev api.Event }
// err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ev)
// if err != nil {
// return err
// }
// firstTimestamp := translateTimestampSince(ev.FirstTimestamp) func (e *Event) Header(ns string) Header {
// if ev.FirstTimestamp.IsZero() { if e.table == nil {
// firstTimestamp = translateMicroTimestampSince(ev.EventTime) return Header{}
// } }
hh := make(Header, 0, len(e.table.ColumnDefinitions))
hh = append(hh, HeaderColumn{Name: "NAMESPACE"})
for _, h := range e.table.ColumnDefinitions {
header := HeaderColumn{Name: strings.ToUpper(h.Name)}
if _, ok := ageCols[header.Name]; ok {
header.Time = true
}
if _, ok := wideCols[header.Name]; ok {
header.Wide = true
}
hh = append(hh, header)
}
// lastTimestamp := translateTimestampSince(ev.LastTimestamp) return hh
// if ev.LastTimestamp.IsZero() { }
// lastTimestamp = firstTimestamp
// }
// count := ev.Count
// if ev.Series != nil {
// lastTimestamp = translateMicroTimestampSince(ev.Series.LastObservedTime)
// count = ev.Series.Count
// } else if count == 0 {
// // Singleton events don't have a count set in the new API.
// count = 1
// }
// var target string // Render renders a K8s resource to screen.
// if len(ev.InvolvedObject.Name) > 0 { func (e *Event) Render(o interface{}, ns string, r *Row) error {
// target = fmt.Sprintf("%s/%s", strings.ToLower(ev.InvolvedObject.Kind), ev.InvolvedObject.Name) row, ok := o.(metav1beta1.TableRow)
// } else { if !ok {
// target = strings.ToLower(ev.InvolvedObject.Kind) return fmt.Errorf("expecting a TableRow but got %T", o)
// } }
nns, name, err := resourceNS(row.Object.Raw)
if err != nil {
return err
}
// r.ID = client.MetaFQN(ev.ObjectMeta) if !ok {
// r.Fields = Fields{ return fmt.Errorf("expecting row 0 to be a string but got %T", row.Cells[0])
// ev.Namespace, }
// lastTimestamp, r.ID = client.FQN(nns, name)
// ev.Type, r.Fields = make(Fields, 0, len(e.Header(ns)))
// ev.Reason, r.Fields = append(r.Fields, nns)
// target, for _, c := range row.Cells {
// ev.InvolvedObject.FieldPath, if c == nil {
// fmtEventSource(ev.Source, ev.ReportingController, ev.ReportingInstance), r.Fields = append(r.Fields, Blank)
// strings.TrimSpace(ev.Message), continue
// firstTimestamp, }
// strconv.Itoa(int(count)), r.Fields = append(r.Fields, fmt.Sprintf("%v", c))
// ev.Name, }
// asStatus(e.diagnose(ev.Type)),
// }
// return nil return nil
// } }
// func translateMicroTimestampSince(timestamp metav1.MicroTime) string {
// if timestamp.IsZero() {
// return "<unknown>"
// }
// return duration.HumanDuration(time.Since(timestamp.Time))
// }
// func translateTimestampSince(timestamp metav1.Time) string {
// if timestamp.IsZero() {
// return "<unknown>"
// }
// return duration.HumanDuration(time.Since(timestamp.Time))
// }
// func fmtEventSource(es api.EventSource, reportingController, reportingInstance string) string {
// return fmtEventSourceComponentInstance(
// firstNonEmpty(es.Component, reportingController),
// firstNonEmpty(es.Host, reportingInstance),
// )
// }
// func fmtEventSourceComponentInstance(component, instance string) string {
// if len(instance) == 0 {
// return component
// }
// return component + ", " + instance
// }
// func firstNonEmpty(ss ...string) string {
// for _, s := range ss {
// if len(s) > 0 {
// return s
// }
// }
// return ""
// }
// // Happy returns true if resource is happy, false otherwise.
// func (Event) diagnose(kind string) error {
// if kind != "Normal" {
// return errors.New("failed event")
// }
// return nil
// }
// // Helpers...
// func asRef(r v1.ObjectReference) string {
// return strings.ToLower(r.Kind) + ":" + r.Name
// }

View File

@ -14,13 +14,13 @@ const ageTableCol = "Age"
// Generic renders a generic resource to screen. // Generic renders a generic resource to screen.
type Generic struct { type Generic struct {
Base
table *metav1beta1.Table table *metav1beta1.Table
ageIndex int ageIndex int
} }
// Happy returns true if resource is happy, false otherwise. func (*Generic) IsGeneric() bool {
func (Generic) Happy(ns string, r Row) bool {
return true return true
} }
@ -30,7 +30,7 @@ func (g *Generic) SetTable(t *metav1beta1.Table) {
} }
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (Generic) ColorerFunc() ColorerFunc { func (*Generic) ColorerFunc() ColorerFunc {
return DefaultColorer return DefaultColorer
} }

View File

@ -15,6 +15,11 @@ import (
// Helm renders a helm chart to screen. // Helm renders a helm chart to screen.
type Helm struct{} type Helm struct{}
// IsGeneric identifies a generic handler.
func (Helm) IsGeneric() bool {
return false
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (Helm) ColorerFunc() ColorerFunc { func (Helm) ColorerFunc() ColorerFunc {
return func(ns string, h Header, re RowEvent) tcell.Color { return func(ns string, h Header, re RowEvent) tcell.Color {

View File

@ -15,11 +15,8 @@ import (
) )
// HorizontalPodAutoscaler renders a K8s HorizontalPodAutoscaler to screen. // HorizontalPodAutoscaler renders a K8s HorizontalPodAutoscaler to screen.
type HorizontalPodAutoscaler struct{} type HorizontalPodAutoscaler struct {
Base
// ColorerFunc colors a resource row.
func (HorizontalPodAutoscaler) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -16,11 +16,8 @@ import (
) )
// Job renders a K8s Job to screen. // Job renders a K8s Job to screen.
type Job struct{} type Job struct {
Base
// ColorerFunc colors a resource row.
func (Job) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -22,11 +22,8 @@ const (
) )
// Node renders a K8s Node to screen. // Node renders a K8s Node to screen.
type Node struct{} type Node struct {
Base
// ColorerFunc colors a resource row.
func (n Node) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -12,11 +12,8 @@ import (
) )
// NetworkPolicy renders a K8s NetworkPolicy to screen. // NetworkPolicy renders a K8s NetworkPolicy to screen.
type NetworkPolicy struct{} type NetworkPolicy struct {
Base
// ColorerFunc colors a resource row.
func (NetworkPolicy) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -13,7 +13,9 @@ import (
) )
// Namespace renders a K8s Namespace to screen. // Namespace renders a K8s Namespace to screen.
type Namespace struct{} type Namespace struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (n Namespace) ColorerFunc() ColorerFunc { func (n Namespace) ColorerFunc() ColorerFunc {

View File

@ -13,11 +13,8 @@ import (
) )
// PodDisruptionBudget renders a K8s PodDisruptionBudget to screen. // PodDisruptionBudget renders a K8s PodDisruptionBudget to screen.
type PodDisruptionBudget struct{} type PodDisruptionBudget struct {
Base
// ColorerFunc colors a resource row.
func (p PodDisruptionBudget) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -17,7 +17,9 @@ import (
) )
// Pod renders a K8s Pod to screen. // Pod renders a K8s Pod to screen.
type Pod struct{} type Pod struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (p Pod) ColorerFunc() ColorerFunc { func (p Pod) ColorerFunc() ColorerFunc {

View File

@ -24,7 +24,9 @@ func rbacVerbHeader() Header {
} }
// Policy renders a rbac policy to screen. // Policy renders a rbac policy to screen.
type Policy struct{} type Policy struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (Policy) ColorerFunc() ColorerFunc { func (Policy) ColorerFunc() ColorerFunc {

View File

@ -15,7 +15,9 @@ import (
) )
// Popeye renders a sanitizer to screen. // Popeye renders a sanitizer to screen.
type Popeye struct{} type Popeye struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (Popeye) ColorerFunc() ColorerFunc { func (Popeye) ColorerFunc() ColorerFunc {

View File

@ -32,7 +32,9 @@ type Forwarder interface {
} }
// PortForward renders a portforwards to screen. // PortForward renders a portforwards to screen.
type PortForward struct{} type PortForward struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (PortForward) ColorerFunc() ColorerFunc { func (PortForward) ColorerFunc() ColorerFunc {

View File

@ -13,7 +13,9 @@ import (
) )
// PersistentVolume renders a K8s PersistentVolume to screen. // PersistentVolume renders a K8s PersistentVolume to screen.
type PersistentVolume struct{} type PersistentVolume struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (p PersistentVolume) ColorerFunc() ColorerFunc { func (p PersistentVolume) ColorerFunc() ColorerFunc {

View File

@ -10,11 +10,8 @@ import (
) )
// PersistentVolumeClaim renders a K8s PersistentVolumeClaim to screen. // PersistentVolumeClaim renders a K8s PersistentVolumeClaim to screen.
type PersistentVolumeClaim struct{} type PersistentVolumeClaim struct {
Base
// ColorerFunc colors a resource row.
func (p PersistentVolumeClaim) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header rbw. // Header returns a header rbw.

View File

@ -30,7 +30,9 @@ var (
) )
// Rbac renders a rbac to screen. // Rbac renders a rbac to screen.
type Rbac struct{} type Rbac struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (Rbac) ColorerFunc() ColorerFunc { func (Rbac) ColorerFunc() ColorerFunc {

View File

@ -10,7 +10,9 @@ import (
) )
// Reference renders a reference to screen. // Reference renders a reference to screen.
type Reference struct{} type Reference struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (Reference) ColorerFunc() ColorerFunc { func (Reference) ColorerFunc() ColorerFunc {

View File

@ -10,11 +10,8 @@ import (
) )
// Role renders a K8s Role to screen. // Role renders a K8s Role to screen.
type Role struct{} type Role struct {
Base
// ColorerFunc colors a resource row.
func (Role) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -11,11 +11,8 @@ import (
) )
// RoleBinding renders a K8s RoleBinding to screen. // RoleBinding renders a K8s RoleBinding to screen.
type RoleBinding struct{} type RoleBinding struct {
Base
// ColorerFunc colors a resource row.
func (RoleBinding) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header rbw. // Header returns a header rbw.

View File

@ -12,7 +12,9 @@ import (
) )
// ReplicaSet renders a K8s ReplicaSet to screen. // ReplicaSet renders a K8s ReplicaSet to screen.
type ReplicaSet struct{} type ReplicaSet struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (r ReplicaSet) ColorerFunc() ColorerFunc { func (r ReplicaSet) ColorerFunc() ColorerFunc {

View File

@ -11,11 +11,8 @@ import (
) )
// ServiceAccount renders a K8s ServiceAccount to screen. // ServiceAccount renders a K8s ServiceAccount to screen.
type ServiceAccount struct{} type ServiceAccount struct {
Base
// ColorerFunc colors a resource row.
func (ServiceAccount) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -10,11 +10,8 @@ import (
) )
// StorageClass renders a K8s StorageClass to screen. // StorageClass renders a K8s StorageClass to screen.
type StorageClass struct{} type StorageClass struct {
Base
// ColorerFunc colors a resource row.
func (StorageClass) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -12,7 +12,9 @@ import (
) )
// ScreenDump renders a screendumps to screen. // ScreenDump renders a screendumps to screen.
type ScreenDump struct{} type ScreenDump struct {
Base
}
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.
func (ScreenDump) ColorerFunc() ColorerFunc { func (ScreenDump) ColorerFunc() ColorerFunc {
@ -21,14 +23,6 @@ func (ScreenDump) ColorerFunc() ColorerFunc {
} }
} }
// DecoratorFunc decorates a string.
type DecoratorFunc func(string) string
// AgeDecorator represents a timestamped as human column.
var AgeDecorator = func(a string) string {
return toAgeHuman(a)
}
// Header returns a header row. // Header returns a header row.
func (ScreenDump) Header(ns string) Header { func (ScreenDump) Header(ns string) Header {
return Header{ return Header{

View File

@ -11,11 +11,8 @@ import (
) )
// StatefulSet renders a K8s StatefulSet to screen. // StatefulSet renders a K8s StatefulSet to screen.
type StatefulSet struct{} type StatefulSet struct {
Base
// ColorerFunc colors a resource row.
func (s StatefulSet) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -9,11 +9,8 @@ import (
) )
// Subject renders a rbac to screen. // Subject renders a rbac to screen.
type Subject struct{} type Subject struct {
Base
// Happy returns true if resource is happy, false otherwise.
func (Subject) Happy(_ string, _ Row) bool {
return true
} }
// ColorerFunc colors a resource row. // ColorerFunc colors a resource row.

View File

@ -13,11 +13,8 @@ import (
) )
// Service renders a K8s Service to screen. // Service renders a K8s Service to screen.
type Service struct{} type Service struct {
Base
// ColorerFunc colors a resource row.
func (Service) ColorerFunc() ColorerFunc {
return DefaultColorer
} }
// Header returns a header row. // Header returns a header row.

View File

@ -88,8 +88,8 @@ func (k ks) CurrentNamespaceName() (string, error) {
return "test", nil return "test", nil
} }
func (k ks) ClusterNames() ([]string, error) { func (k ks) ClusterNames() (map[string]struct{}, error) {
return []string{"test"}, nil return map[string]struct{}{"test": {}}, nil
} }
func (k ks) NamespaceNames(nn []v1.Namespace) []string { func (k ks) NamespaceNames(nn []v1.Namespace) []string {

View File

@ -276,10 +276,10 @@ func (a *App) Resume() {
go a.clusterUpdater(ctx) go a.clusterUpdater(ctx)
if err := a.StylesWatcher(ctx, a); err != nil { if err := a.StylesWatcher(ctx, a); err != nil {
log.Error().Err(err).Msgf("Styles watcher failed") log.Warn().Err(err).Msgf("Styles watcher failed")
} }
if err := a.CustomViewsWatcher(ctx, a); err != nil { if err := a.CustomViewsWatcher(ctx, a); err != nil {
log.Error().Err(err).Msgf("CustomView watcher failed") log.Warn().Err(err).Msgf("CustomView watcher failed")
} }
} }
@ -357,6 +357,10 @@ func (a *App) switchNS(ns string) error {
if ns == client.ClusterScope { if ns == client.ClusterScope {
ns = client.AllNamespaces ns = client.AllNamespaces
} }
if ns == a.Config.ActiveNamespace() {
return nil
}
ok, err := a.isValidNS(ns) ok, err := a.isValidNS(ns)
if err != nil { if err != nil {
return err return err
@ -365,7 +369,10 @@ func (a *App) switchNS(ns string) error {
return fmt.Errorf("Invalid namespace %q", ns) return fmt.Errorf("Invalid namespace %q", ns)
} }
if err := a.Config.SetActiveNamespace(ns); err != nil { if err := a.Config.SetActiveNamespace(ns); err != nil {
return fmt.Errorf("Unable to save active namespace in config") return err
}
if err := a.Config.Save(); err != nil {
return err
} }
return a.factory.SetActiveNS(ns) return a.factory.SetActiveNS(ns)

View File

@ -134,9 +134,6 @@ func (b *Browser) Start() {
if err := b.app.switchNS(ns); err != nil { if err := b.app.switchNS(ns); err != nil {
log.Error().Err(err).Msgf("ns switch failed") log.Error().Err(err).Msgf("ns switch failed")
} }
if err := b.app.Config.Save(); err != nil {
log.Error().Err(err).Msgf("Config Save")
}
b.Stop() b.Stop()
b.GetModel().AddListener(b) b.GetModel().AddListener(b)

View File

@ -17,7 +17,8 @@ func NewEvent(gvr client.GVR) ResourceViewer {
e := Event{ e := Event{
ResourceViewer: NewBrowser(gvr), ResourceViewer: NewBrowser(gvr),
} }
e.GetTable().SetColorerFn(render.Event{}.ColorerFunc()) var r *render.Event
e.GetTable().SetColorerFn(r.ColorerFunc())
e.AddBindKeysFn(e.bindKeys) e.AddBindKeysFn(e.bindKeys)
e.GetTable().SetSortCol("LAST SEEN", false) e.GetTable().SetSortCol("LAST SEEN", false)
@ -25,9 +26,10 @@ func NewEvent(gvr client.GVR) ResourceViewer {
} }
func (e *Event) bindKeys(aa ui.KeyActions) { func (e *Event) bindKeys(aa ui.KeyActions) {
aa.Delete(tcell.KeyCtrlD, ui.KeyE) aa.Delete(tcell.KeyCtrlD, ui.KeyE, ui.KeyA)
aa.Add(ui.KeyActions{ aa.Add(ui.KeyActions{
ui.KeyShiftL: ui.NewKeyAction("Sort LastSeen", e.GetTable().SortColCmd("LAST SEEN", false), false), ui.KeyShiftL: ui.NewKeyAction("Sort LastSeen", e.GetTable().SortColCmd("LAST SEEN", false), false),
ui.KeyShiftF: ui.NewKeyAction("Sort FirstSeen", e.GetTable().SortColCmd("FIRST SEEN", false), false),
ui.KeyShiftT: ui.NewKeyAction("Sort Type", e.GetTable().SortColCmd("TYPE", true), false), ui.KeyShiftT: ui.NewKeyAction("Sort Type", e.GetTable().SortColCmd("TYPE", true), false),
ui.KeyShiftR: ui.NewKeyAction("Sort Reason", e.GetTable().SortColCmd("REASON", true), false), ui.KeyShiftR: ui.NewKeyAction("Sort Reason", e.GetTable().SortColCmd("REASON", true), false),
ui.KeyShiftS: ui.NewKeyAction("Sort Source", e.GetTable().SortColCmd("SOURCE", true), false), ui.KeyShiftS: ui.NewKeyAction("Sort Source", e.GetTable().SortColCmd("SOURCE", true), false),

View File

@ -171,8 +171,8 @@ func (k ks) CurrentNamespaceName() (string, error) {
return "test", nil return "test", nil
} }
func (k ks) ClusterNames() ([]string, error) { func (k ks) ClusterNames() (map[string]struct{}, error) {
return []string{"test"}, nil return map[string]struct{}{"test": {}}, nil
} }
func (k ks) NamespaceNames(nn []v1.Namespace) []string { func (k ks) NamespaceNames(nn []v1.Namespace) []string {

View File

@ -10,7 +10,9 @@ import (
) )
// Section represents an xray renderer. // Section represents an xray renderer.
type Section struct{} type Section struct {
render.Base
}
// Render renders an xray node. // Render renders an xray node.
func (s *Section) Render(ctx context.Context, ns string, o interface{}) error { func (s *Section) Render(ctx context.Context, ns string, o interface{}) error {