From f7badc4a2aaea4c44e71aec2987fcccf8806166a Mon Sep 17 00:00:00 2001 From: derailed Date: Wed, 5 Feb 2020 21:10:27 -0800 Subject: [PATCH] fix #523 #522 #521 --- change_logs/release_v0.13.8.md | 29 ++++++++++++++ internal/config/alias.go | 61 +++++++++++++++++++++++++----- internal/dao/alias.go | 32 ++++------------ internal/dao/alias_test.go | 2 +- internal/dao/registry.go | 53 +++++++++++++++++++------- internal/model/log.go | 23 +++++++++-- internal/model/log_test.go | 2 +- internal/model/stack.go | 28 ++++++++++++-- internal/model/table.go | 19 +++++++--- internal/render/table_data.go | 36 +++++++----------- internal/render/table_data_test.go | 6 +-- internal/ui/table.go | 3 -- internal/view/app.go | 7 ++++ internal/view/browser.go | 3 +- internal/view/container.go | 5 --- internal/view/log.go | 4 +- internal/view/svc_test.go | 28 +++++++------- internal/view/xray.go | 6 +-- internal/watch/factory.go | 3 ++ internal/xray/tree_node.go | 2 +- 20 files changed, 236 insertions(+), 116 deletions(-) create mode 100644 change_logs/release_v0.13.8.md diff --git a/change_logs/release_v0.13.8.md b/change_logs/release_v0.13.8.md new file mode 100644 index 00000000..0bf5ce4b --- /dev/null +++ b/change_logs/release_v0.13.8.md @@ -0,0 +1,29 @@ + + +# Release v0.13.8 + +## 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 is as ever very much noticed and appreciated! + +Also if you dig this tool, please 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) + +--- + +### GH Sponsorships + +WOOT!! Big Thank you to [Mark Baumann](https://github.com/mtreeman) for your contributions and support for K9s! + +--- + +## Resolved Bugs/Features/PRs + +* [Issue #523](https://github.com/derailed/k9s/issues/523) +* [Issue #522](https://github.com/derailed/k9s/issues/522) +* [Issue #521](https://github.com/derailed/k9s/issues/521) + +--- + + © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/internal/config/alias.go b/internal/config/alias.go index bc1dcbd2..25f76bbb 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -3,6 +3,7 @@ package config import ( "io/ioutil" "path/filepath" + "sync" "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" @@ -20,16 +21,17 @@ type ShortNames map[string][]string // Aliases represents a collection of aliases. type Aliases struct { Alias Alias `yaml:"alias"` + mx sync.RWMutex } // NewAliases return a new alias. -func NewAliases() Aliases { - return Aliases{ +func NewAliases() *Aliases { + return &Aliases{ Alias: make(Alias, 50), } } -func (a Aliases) loadDefaults() { +func (a *Aliases) loadDefaults() { const ( contexts = "contexts" portFwds = "portforwards" @@ -39,6 +41,9 @@ func (a Aliases) loadDefaults() { users = "users" ) + a.mx.Lock() + defer a.mx.Unlock() + a.Alias["dp"] = "apps/v1/deployments" a.Alias["sec"] = "v1/secrets" a.Alias["jo"] = "batch/v1/jobs" @@ -80,19 +85,52 @@ func (a Aliases) loadDefaults() { } // Load K9s aliases. -func (a Aliases) Load() error { +func (a *Aliases) Load() error { a.loadDefaults() return a.LoadAliases(K9sAlias) } +// ShortNames return all shortnames. +func (a *Aliases) ShortNames() ShortNames { + a.mx.RLock() + defer a.mx.RUnlock() + + m := make(ShortNames, len(a.Alias)) + for alias, gvr := range a.Alias { + if _, ok := m[gvr]; ok { + m[gvr] = append(m[gvr], alias) + } else { + m[gvr] = []string{alias} + } + } + + return m +} + +// Clear remove all aliases. +func (a *Aliases) Clear() { + a.mx.Lock() + defer a.mx.Unlock() + + for k := range a.Alias { + delete(a.Alias, k) + } +} + // Get retrieves an alias. -func (a Aliases) Get(k string) (string, bool) { +func (a *Aliases) Get(k string) (string, bool) { + a.mx.RLock() + defer a.mx.RUnlock() + v, ok := a.Alias[k] return v, ok } // Define declares a new alias. -func (a Aliases) Define(gvr string, aliases ...string) { +func (a *Aliases) Define(gvr string, aliases ...string) { + a.mx.Lock() + defer a.mx.Unlock() + for _, alias := range aliases { if _, ok := a.Alias[alias]; ok { continue @@ -102,17 +140,20 @@ func (a Aliases) Define(gvr string, aliases ...string) { } // LoadAliases loads alias from a given file. -func (a Aliases) LoadAliases(path string) error { +func (a *Aliases) LoadAliases(path string) error { f, err := ioutil.ReadFile(path) if err != nil { log.Warn().Err(err).Msgf("No custom aliases found") return nil } - var aa Aliases + var aa *Aliases if err := yaml.Unmarshal(f, &aa); err != nil { return err } + + a.mx.Lock() + defer a.mx.Unlock() for k, v := range aa.Alias { a.Alias[k] = v } @@ -121,13 +162,13 @@ func (a Aliases) LoadAliases(path string) error { } // Save alias to disk. -func (a Aliases) Save() error { +func (a *Aliases) Save() error { log.Debug().Msg("[Config] Saving Aliases...") return a.SaveAliases(K9sAlias) } // SaveAliases saves aliases to a given file. -func (a Aliases) SaveAliases(path string) error { +func (a *Aliases) SaveAliases(path string) error { EnsurePath(path, DefaultDirMod) cfg, err := yaml.Marshal(a) if err != nil { diff --git a/internal/dao/alias.go b/internal/dao/alias.go index 29189e3b..c7ea723f 100644 --- a/internal/dao/alias.go +++ b/internal/dao/alias.go @@ -3,6 +3,7 @@ package dao import ( "context" "errors" + "fmt" "sort" "strings" @@ -18,7 +19,7 @@ var _ Accessor = (*Alias)(nil) // Alias tracks standard and custom command aliases. type Alias struct { NonResource - config.Aliases + *config.Aliases } // NewAlias returns a new set of aliases. @@ -29,13 +30,6 @@ func NewAlias(f Factory) *Alias { return &a } -// Clear remove all aliases. -func (a *Alias) Clear() { - for k := range a.Alias { - delete(a.Alias, k) - } -} - // Check verifies an alias is defined for this command. func (a *Alias) Check(cmd string) bool { _, ok := a.Aliases.Get(cmd) @@ -43,22 +37,12 @@ func (a *Alias) Check(cmd string) bool { } // List returns a collection of aliases. -// BOZO!! Already have aliases here. Refact!! func (a *Alias) List(ctx context.Context, _ string) ([]runtime.Object, error) { - a, ok := ctx.Value(internal.KeyAliases).(*Alias) + aa, ok := ctx.Value(internal.KeyAliases).(*Alias) if !ok { - return nil, errors.New("no aliases found in context") + return nil, fmt.Errorf("expecting *Alias but got %T", ctx.Value(internal.KeyAliases)) } - - m := make(config.ShortNames, len(a.Alias)) - for alias, gvr := range a.Alias { - if _, ok := m[gvr]; ok { - m[gvr] = append(m[gvr], alias) - } else { - m[gvr] = []string{alias} - } - } - + m := aa.ShortNames() oo := make([]runtime.Object, 0, len(m)) for gvr, aliases := range m { sort.StringSlice(aliases).Sort() @@ -84,7 +68,7 @@ func (a *Alias) Get(_ context.Context, _ string) (runtime.Object, error) { // Ensure makes sure alias are loaded. func (a *Alias) Ensure() (config.Alias, error) { - if err := LoadResources(a.Factory); err != nil { + if err := MetaAccess.LoadResources(a.Factory); err != nil { return config.Alias{}, err } return a.Alias, a.load() @@ -95,8 +79,8 @@ func (a *Alias) load() error { return err } - for _, gvr := range AllGVRs() { - meta, err := MetaFor(gvr) + for _, gvr := range MetaAccess.AllGVRs() { + meta, err := MetaAccess.MetaFor(gvr) if err != nil { return err } diff --git a/internal/dao/alias_test.go b/internal/dao/alias_test.go index b77638de..0924ee27 100644 --- a/internal/dao/alias_test.go +++ b/internal/dao/alias_test.go @@ -33,7 +33,7 @@ func TestAliasList(t *testing.T) { func makeAliases() *dao.Alias { return &dao.Alias{ - Aliases: config.Aliases{ + Aliases: &config.Aliases{ Alias: config.Alias{ "fred": "v1/fred", "f": "v1/fred", diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 438f18ea..56799b5b 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -4,6 +4,7 @@ import ( "fmt" "sort" "strings" + "sync" "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog/log" @@ -13,7 +14,19 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -var resMetas = ResourceMetas{} +// MetaAccess tracks resources metadata. +var MetaAccess = NewMeta() + +// Meta represents available resource metas. +type Meta struct { + resMetas ResourceMetas + mx sync.RWMutex +} + +// NewMeta returns a resource meta. +func NewMeta() *Meta { + return &Meta{resMetas: make(ResourceMetas)} +} // AccessorFor returns a client accessor for a resource if registered. // Otherwise it returns a generic accessor. @@ -47,14 +60,20 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { } // RegisterMeta registers a new resource meta object. -func RegisterMeta(gvr string, res metav1.APIResource) { - resMetas[client.NewGVR(gvr)] = res +func (m *Meta) RegisterMeta(gvr string, res metav1.APIResource) { + m.mx.Lock() + defer m.mx.Unlock() + + m.resMetas[client.NewGVR(gvr)] = res } // AllGVRs returns all cluster resources. -func AllGVRs() client.GVRs { - kk := make(client.GVRs, 0, len(resMetas)) - for k := range resMetas { +func (m *Meta) AllGVRs() client.GVRs { + m.mx.RLock() + defer m.mx.RUnlock() + + kk := make(client.GVRs, 0, len(m.resMetas)) + for k := range m.resMetas { kk = append(kk, k) } sort.Sort(kk) @@ -63,12 +82,15 @@ func AllGVRs() client.GVRs { } // MetaFor returns a resource metadata for a given gvr. -func MetaFor(gvr client.GVR) (metav1.APIResource, error) { - m, ok := resMetas[gvr] +func (m *Meta) MetaFor(gvr client.GVR) (metav1.APIResource, error) { + m.mx.RLock() + defer m.mx.RUnlock() + + meta, ok := m.resMetas[gvr] if !ok { return metav1.APIResource{}, fmt.Errorf("no resource meta defined for %q", gvr) } - return m, nil + return meta, nil } // IsK8sMeta checks for non resource meta. @@ -94,13 +116,16 @@ func IsK9sMeta(m metav1.APIResource) bool { } // LoadResources hydrates server preferred+CRDs resource metadata. -func LoadResources(f Factory) error { - resMetas = make(ResourceMetas, 100) - if err := loadPreferred(f, resMetas); err != nil { +func (m *Meta) LoadResources(f Factory) error { + m.mx.Lock() + defer m.mx.Unlock() + + m.resMetas = make(ResourceMetas, 100) + if err := loadPreferred(f, m.resMetas); err != nil { return err } - loadNonResource(resMetas) - loadCRDs(f, resMetas) + loadNonResource(m.resMetas) + loadCRDs(f, m.resMetas) return nil } diff --git a/internal/model/log.go b/internal/model/log.go index 933864b8..896f00be 100644 --- a/internal/model/log.go +++ b/internal/model/log.go @@ -15,6 +15,8 @@ import ( "github.com/sahilm/fuzzy" ) +const logMaxBufferSize = 50 + // LogsListener represents a log model listener. type LogsListener interface { // LogChanged notifies the model changed. @@ -65,8 +67,10 @@ func (l *Log) Init(f dao.Factory) { // Clear the logs. func (l *Log) Clear() { l.mx.Lock() - defer l.mx.Unlock() - l.lines, l.lastSent = []string{}, 0 + { + l.lines, l.lastSent = []string{}, 0 + } + l.mx.Unlock() l.fireLogCleared() } @@ -74,6 +78,7 @@ func (l *Log) Clear() { func (l *Log) Start() { if err := l.load(); err != nil { log.Error().Err(err).Msgf("Tail logs failed!") + l.fireLogError(err) } } @@ -91,6 +96,7 @@ func (l *Log) Stop() { func (l *Log) Set(lines []string) { l.mx.Lock() defer l.mx.Unlock() + l.lines = lines l.fireLogChanged(lines) } @@ -99,6 +105,7 @@ func (l *Log) Set(lines []string) { func (l *Log) ClearFilter() { l.mx.RLock() defer l.mx.RUnlock() + l.filter = "" l.fireLogChanged(l.lines) } @@ -133,7 +140,7 @@ func (l *Log) load() error { } logger, ok := accessor.(dao.Loggable) if !ok { - return fmt.Errorf("Resource %s is not tailable", l.gvr) + return fmt.Errorf("Resource %s is not Loggable", l.gvr) } if err := logger.TailLogs(ctx, c, l.logOptions); err != nil { @@ -152,6 +159,7 @@ func (l *Log) Append(line string) { if line == "" { return } + l.mx.Lock() defer l.mx.Unlock() @@ -196,6 +204,15 @@ func (l *Log) updateLogs(ctx context.Context, c <-chan string) { return } l.Append(line) + var overflow bool + l.mx.RLock() + { + overflow = len(l.lines)-l.lastSent > logMaxBufferSize + } + l.mx.RUnlock() + if overflow { + l.Notify(true) + } case <-time.After(200 * time.Millisecond): l.Notify(true) case <-ctx.Done(): diff --git a/internal/model/log_test.go b/internal/model/log_test.go index ddb3efcb..6b23ea46 100644 --- a/internal/model/log_test.go +++ b/internal/model/log_test.go @@ -105,7 +105,7 @@ func TestLogStartStop(t *testing.T) { assert.Equal(t, 1, v.dataCalled) assert.Equal(t, 1, v.clearCalled) - assert.Equal(t, 0, v.errCalled) + assert.Equal(t, 1, v.errCalled) assert.Equal(t, 2, len(v.data)) } diff --git a/internal/model/stack.go b/internal/model/stack.go index 9e8e9808..b2ffb3cb 100644 --- a/internal/model/stack.go +++ b/internal/model/stack.go @@ -1,6 +1,8 @@ package model import ( + "sync" + "github.com/rs/zerolog/log" ) @@ -40,6 +42,7 @@ type StackListener interface { type Stack struct { components []Component listeners []StackListener + mx sync.RWMutex } // NewStack returns a new initialized stack. @@ -49,6 +52,9 @@ func NewStack() *Stack { // Flatten returns a string representation of the component stack. func (s *Stack) Flatten() []string { + s.mx.RLock() + defer s.mx.RUnlock() + ss := make([]string, len(s.components)) for i, c := range s.components { ss[i] = c.Name() @@ -84,7 +90,12 @@ func (s *Stack) Push(c Component) { if top := s.Top(); top != nil { top.Stop() } - s.components = append(s.components, c) + + s.mx.Lock() + { + s.components = append(s.components, c) + } + s.mx.Unlock() s.notify(StackPush, c) } @@ -94,8 +105,13 @@ func (s *Stack) Pop() (Component, bool) { return nil, false } - c := s.components[s.size()] - s.components = s.components[:s.size()] + var c Component + s.mx.Lock() + { + c = s.components[s.size()] + s.components = s.components[:s.size()] + } + s.mx.Unlock() s.notify(StackPop, c) return c, true @@ -103,6 +119,9 @@ func (s *Stack) Pop() (Component, bool) { // Peek returns stack state. func (s *Stack) Peek() []Component { + s.mx.RLock() + defer s.mx.RUnlock() + return s.components } @@ -115,6 +134,9 @@ func (s *Stack) Clear() { // Empty returns true if the stack is empty. func (s *Stack) Empty() bool { + s.mx.RLock() + defer s.mx.RUnlock() + return len(s.components) == 0 } diff --git a/internal/model/table.go b/internal/model/table.go index c5893b35..327d5586 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -3,6 +3,7 @@ package model import ( "context" "fmt" + "sync" "sync/atomic" "time" @@ -35,6 +36,7 @@ type Table struct { inUpdate int32 refreshRate time.Duration instance string + mx sync.RWMutex } // NewTable returns a new table model. @@ -170,7 +172,10 @@ func (t *Table) Empty() bool { // Peek returns model data. func (t *Table) Peek() render.TableData { - return *t.data + t.mx.RLock() + defer t.mx.RUnlock() + + return t.data.Clone() } func (t *Table) updater(ctx context.Context) { @@ -200,7 +205,7 @@ func (t *Table) refresh(ctx context.Context) { t.fireTableLoadFailed(err) return } - t.fireTableChanged(*t.data) + t.fireTableChanged() } func (t *Table) list(ctx context.Context, a dao.Accessor) ([]runtime.Object, error) { @@ -252,15 +257,16 @@ func (t *Table) reconcile(ctx context.Context) error { } } - t.data.Mutex.Lock() - defer t.data.Mutex.Unlock() + t.mx.Lock() + defer t.mx.Unlock() + // if labelSelector in place might as well clear the model data. sel, ok := ctx.Value(internal.KeyLabels).(string) if ok && sel != "" { t.data.Clear() } t.data.Update(rows) - t.data.Namespace, t.data.Header = t.namespace, meta.Renderer.Header(t.namespace) + t.data.SetHeader(t.namespace, meta.Renderer.Header(t.namespace)) return nil } @@ -292,7 +298,8 @@ func (t *Table) resourceMeta() ResourceMeta { return meta } -func (t *Table) fireTableChanged(data render.TableData) { +func (t *Table) fireTableChanged() { + data := t.Peek() for _, l := range t.listeners { l.TableDataChanged(data) } diff --git a/internal/render/table_data.go b/internal/render/table_data.go index 9cb7f293..d907277a 100644 --- a/internal/render/table_data.go +++ b/internal/render/table_data.go @@ -1,20 +1,15 @@ package render -import ( - "sync" -) - // TableData tracks a K8s resource for tabular display. type TableData struct { Header HeaderRow RowEvents RowEvents Namespace string - Mutex *sync.RWMutex } // NewTableData returns a new table. func NewTableData() *TableData { - return &TableData{Mutex: &sync.RWMutex{}} + return &TableData{} } // Clear clears out the entire table. @@ -31,13 +26,18 @@ func cloneTable(t TableData) TableData { return t } +// SetHeader sets table header. +func (t *TableData) SetHeader(ns string, h HeaderRow) { + t.Namespace, t.Header = ns, h +} + // Update computes row deltas and update the table data. func (t *TableData) Update(rows Rows) { empty := len(t.RowEvents) == 0 - kk := make([]string, 0, len(rows)) + kk := make(map[string]struct{}, len(rows)) var blankDelta DeltaRow for _, row := range rows { - kk = append(kk, row.ID) + kk[row.ID] = struct{}{} if empty { t.RowEvents = append(t.RowEvents, NewRowEvent(EventAdd, row)) continue @@ -61,19 +61,11 @@ func (t *TableData) Update(rows Rows) { } } -// Delete delete items in cache that are no longer valid. -func (t *TableData) Delete(newKeys []string) { +// Delete removes items in cache that are no longer valid. +func (t *TableData) Delete(newKeys map[string]struct{}) { var victims []string for _, re := range t.RowEvents { - var found bool - for i, key := range newKeys { - if key == re.Row.ID { - found = true - newKeys = append(newKeys[:i], newKeys[i+1:]...) - break - } - } - if !found { + if _, ok := newKeys[re.Row.ID]; !ok { victims = append(victims, re.Row.ID) } } @@ -88,12 +80,10 @@ func (t *TableData) Diff(table TableData) bool { if t.Namespace != table.Namespace { return true } + if t.Header.Diff(table.Header) { return true } - if t.RowEvents.Diff(table.RowEvents) { - return true - } - return false + return t.RowEvents.Diff(table.RowEvents) } diff --git a/internal/render/table_data_test.go b/internal/render/table_data_test.go index 10391f4f..fa1dc7a4 100644 --- a/internal/render/table_data_test.go +++ b/internal/render/table_data_test.go @@ -10,7 +10,7 @@ import ( func TestTableDataDelete(t *testing.T) { uu := map[string]struct { re render.RowEvents - kk []string + kk map[string]struct{} e render.RowEvents }{ "ordered": { @@ -19,7 +19,7 @@ func TestTableDataDelete(t *testing.T) { {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, }, - kk: []string{"A", "C"}, + kk: map[string]struct{}{"A": struct{}{}, "C": struct{}{}}, e: render.RowEvents{ {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, @@ -32,7 +32,7 @@ func TestTableDataDelete(t *testing.T) { {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, {Row: render.Row{ID: "D", Fields: render.Fields{"10", "2", "3"}}}, }, - kk: []string{"C", "A"}, + kk: map[string]struct{}{"C": struct{}{}, "A": struct{}{}}, e: render.RowEvents{ {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, diff --git a/internal/ui/table.go b/internal/ui/table.go index eee05742..2c091cf2 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -163,9 +163,6 @@ func (t *Table) SetSortCol(index, count int, asc bool) { // Update table content. func (t *Table) Update(data render.TableData) { - data.Mutex.RLock() - defer data.Mutex.RUnlock() - if t.decorateFn != nil { data = t.decorateFn(data) } diff --git a/internal/view/app.go b/internal/view/app.go index e9c64dab..43efb7f8 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "sync" "time" "github.com/derailed/k9s/internal" @@ -40,6 +41,7 @@ type App struct { cancelFn context.CancelFunc conRetry int clusterModel *model.ClusterInfo + mx sync.Mutex } // NewApp returns a K9s app instance. @@ -59,6 +61,8 @@ func NewApp(cfg *config.Config) *App { // ConOK checks the connection is cool, returns false otherwise. func (a *App) ConOK() bool { + a.mx.Lock() + defer a.mx.Unlock() return a.conRetry == 0 } @@ -194,6 +198,9 @@ func (a *App) clusterUpdater(ctx context.Context) { } func (a *App) refreshCluster() { + a.mx.Lock() + defer a.mx.Unlock() + c := a.Content.Top() if ok := a.Conn().CheckConnectivity(); ok { if a.conRetry > 0 { diff --git a/internal/view/browser.go b/internal/view/browser.go index a9f8cc86..4e471f59 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -40,7 +40,7 @@ func NewBrowser(gvr client.GVR) ResourceViewer { // Init watches all running pods in given namespace func (b *Browser) Init(ctx context.Context) error { var err error - b.meta, err = dao.MetaFor(b.gvr) + b.meta, err = dao.MetaAccess.MetaFor(b.gvr) if err != nil { return err } @@ -55,6 +55,7 @@ func (b *Browser) Init(ctx context.Context) error { return e } } + b.app.CmdBuff().Reset() b.bindKeys() if b.bindKeysFn != nil { diff --git a/internal/view/container.go b/internal/view/container.go index 18f1684e..9b1c150a 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -69,11 +69,6 @@ func (c *Container) selectedContainer() string { } func (c *Container) viewLogs(app *App, model ui.Tabular, gvr, path string) { - status := c.GetTable().GetSelectedCell(3) - if status != "Running" && status != "Completed" { - app.Flash().Err(errors.New("No logs available")) - return - } c.ResourceViewer.(*LogsExtender).showLogs(c.GetTable().Path, false) } diff --git a/internal/view/log.go b/internal/view/log.go index fdca9fc1..3865590e 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -103,7 +103,9 @@ func (l *Log) LogCleared() { // LogFailed notifies an error occurred. func (l *Log) LogFailed(err error) { - l.app.Flash().Err(err) + l.app.QueueUpdateDraw(func() { + l.app.Flash().Err(err) + }) } // LogChanged updates the logs. diff --git a/internal/view/svc_test.go b/internal/view/svc_test.go index b1b4d364..b09e3dfc 100644 --- a/internal/view/svc_test.go +++ b/internal/view/svc_test.go @@ -11,7 +11,7 @@ import ( ) func init() { - dao.RegisterMeta("v1/pods", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("v1/pods", metav1.APIResource{ Name: "pods", SingularName: "pod", Namespaced: true, @@ -19,7 +19,7 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) - dao.RegisterMeta("v1/namespaces", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("v1/namespaces", metav1.APIResource{ Name: "namespaces", SingularName: "namespace", Namespaced: true, @@ -27,7 +27,7 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) - dao.RegisterMeta("v1/services", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("v1/services", metav1.APIResource{ Name: "services", SingularName: "service", Namespaced: true, @@ -35,7 +35,7 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) - dao.RegisterMeta("v1/secrets", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("v1/secrets", metav1.APIResource{ Name: "secrets", SingularName: "secret", Namespaced: true, @@ -44,7 +44,7 @@ func init() { Categories: []string{"k9s"}, }) - dao.RegisterMeta("aliases", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("aliases", metav1.APIResource{ Name: "aliases", SingularName: "alias", Namespaced: true, @@ -52,7 +52,7 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) - dao.RegisterMeta("containers", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("containers", metav1.APIResource{ Name: "containers", SingularName: "container", Namespaced: true, @@ -60,7 +60,7 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) - dao.RegisterMeta("contexts", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("contexts", metav1.APIResource{ Name: "contexts", SingularName: "context", Namespaced: true, @@ -68,7 +68,7 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) - dao.RegisterMeta("subjects", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("subjects", metav1.APIResource{ Name: "subjects", SingularName: "subject", Namespaced: true, @@ -76,7 +76,7 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) - dao.RegisterMeta("rbac", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("rbac", metav1.APIResource{ Name: "rbacs", SingularName: "rbac", Namespaced: true, @@ -84,7 +84,7 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) - dao.RegisterMeta("portforwards", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("portforwards", metav1.APIResource{ Name: "portforwards", SingularName: "portforward", Namespaced: true, @@ -93,7 +93,7 @@ func init() { Categories: []string{"k9s"}, }) - dao.RegisterMeta("screendumps", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("screendumps", metav1.APIResource{ Name: "screendumps", SingularName: "screendump", Namespaced: true, @@ -101,7 +101,7 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) - dao.RegisterMeta("apps/v1/statefulsets", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("apps/v1/statefulsets", metav1.APIResource{ Name: "statefulsets", SingularName: "statefulset", Namespaced: true, @@ -109,7 +109,7 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) - dao.RegisterMeta("apps/v1/daemonsets", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("apps/v1/daemonsets", metav1.APIResource{ Name: "daemonsets", SingularName: "daemonset", Namespaced: true, @@ -117,7 +117,7 @@ func init() { Verbs: []string{"get", "list", "watch", "delete"}, Categories: []string{"k9s"}, }) - dao.RegisterMeta("apps/v1/deployments", metav1.APIResource{ + dao.MetaAccess.RegisterMeta("apps/v1/deployments", metav1.APIResource{ Name: "deployments", SingularName: "deployment", Namespaced: true, diff --git a/internal/view/xray.go b/internal/view/xray.go index acdfcc15..d16ce4bc 100644 --- a/internal/view/xray.go +++ b/internal/view/xray.go @@ -56,7 +56,7 @@ func (x *Xray) Init(ctx context.Context) error { x.SetKeyListenerFn(x.keyEntered) var err error - x.meta, err = dao.MetaFor(x.gvr) + x.meta, err = dao.MetaAccess.MetaFor(x.gvr) if err != nil { return err } @@ -138,7 +138,7 @@ func (x *Xray) refreshActions() { } var err error - x.meta, err = dao.MetaFor(client.NewGVR(ref.GVR)) + x.meta, err = dao.MetaAccess.MetaFor(client.NewGVR(ref.GVR)) if err != nil { log.Warn().Msgf("NO meta for %q -- %s", ref.GVR, err) return @@ -288,7 +288,7 @@ func (x *Xray) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { defer x.Start() { gvr := client.NewGVR(ref.GVR) - meta, err := dao.MetaFor(gvr) + meta, err := dao.MetaAccess.MetaFor(gvr) if err != nil { log.Warn().Msgf("NO meta for %q -- %s", ref.GVR, err) return nil diff --git a/internal/watch/factory.go b/internal/watch/factory.go index ce6d7586..8b1d9849 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -150,6 +150,9 @@ func (f *Factory) SetActiveNS(ns string) { } func (f *Factory) isClusterWide() bool { + f.mx.RLock() + defer f.mx.RUnlock() + _, ok := f.factories[client.AllNamespaces] return ok } diff --git a/internal/xray/tree_node.go b/internal/xray/tree_node.go index 7e0c93ac..bab59321 100644 --- a/internal/xray/tree_node.go +++ b/internal/xray/tree_node.go @@ -336,7 +336,7 @@ func dumpStdOut(n *TreeNode, level int) { } func category(gvr string) string { - meta, err := dao.MetaFor(client.NewGVR(gvr)) + meta, err := dao.MetaAccess.MetaFor(client.NewGVR(gvr)) if err != nil { return "" }