From 5c0fc0845b3be6a76e6154d316510d047023ea31 Mon Sep 17 00:00:00 2001 From: derailed Date: Sun, 29 Dec 2019 12:53:16 -0700 Subject: [PATCH] checkpoint --- internal/client/config.go | 13 ++++- internal/model/table.go | 13 +++-- internal/render/table.go | 8 +++ internal/ui/table.go | 3 ++ internal/ui/table_test.go | 37 +++++++------- internal/view/app.go | 4 +- internal/view/dp.go | 4 +- internal/view/ds.go | 4 +- internal/view/log.go | 1 - internal/view/logs_extender.go | 8 ++- internal/view/sts.go | 4 +- internal/view/table_int_test.go | 86 +++++++++++++++++---------------- main.go | 8 --- 13 files changed, 112 insertions(+), 81 deletions(-) diff --git a/internal/client/config.go b/internal/client/config.go index f54dae80..364e6236 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -3,6 +3,7 @@ package client import ( "errors" "fmt" + "sync" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" @@ -19,11 +20,15 @@ type Config struct { currentContext string rawConfig *clientcmdapi.Config restConfig *restclient.Config + mutex *sync.RWMutex } // NewConfig returns a new k8s config or an error if the flags are invalid. func NewConfig(f *genericclioptions.ConfigFlags) *Config { - return &Config{flags: f} + return &Config{ + flags: f, + mutex: &sync.RWMutex{}, + } } // Flags returns configuration flags. @@ -231,12 +236,18 @@ func (c *Config) NamespaceNames(nns []v1.Namespace) []string { // ConfigAccess return the current kubeconfig api server access configuration. func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) { + c.mutex.RLock() + defer c.mutex.RUnlock() + c.ensureConfig() return c.clientConfig.ConfigAccess(), nil } // RawConfig fetch the current kubeconfig with no overrides. func (c *Config) RawConfig() (clientcmdapi.Config, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + if c.rawConfig != nil { if c.rawConfig.CurrentContext == c.currentContext { return *c.rawConfig, nil diff --git a/internal/model/table.go b/internal/model/table.go index 169ac222..86023ebc 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -22,7 +22,7 @@ type TableListener interface { type Table struct { gvr string namespace string - data render.TableData + data *render.TableData listeners []TableListener inUpdate int32 refreshRate time.Duration @@ -32,7 +32,7 @@ type Table struct { func NewTable(gvr string) *Table { return &Table{ gvr: gvr, - data: render.TableData{}, + data: render.NewTableData(), refreshRate: 2 * time.Second, } } @@ -81,7 +81,7 @@ func (t *Table) Empty() bool { // Peek returns model data. func (t *Table) Peek() render.TableData { - return t.data + return *t.data } func (t *Table) updater(ctx context.Context) { @@ -107,13 +107,13 @@ func (t *Table) refresh(ctx context.Context) { log.Error().Err(err).Msg("Reconcile failed") t.fireTableLoadFailed(err) } - t.fireTableChanged(t.data) + t.fireTableChanged(*t.data) } // AddListener adds a new model listener. func (t *Table) AddListener(l TableListener) { t.listeners = append(t.listeners, l) - t.fireTableChanged(t.data) + t.fireTableChanged(*t.data) } // RemoveListener delete a listener from the list. @@ -144,6 +144,9 @@ func (t *Table) fireTableLoadFailed(err error) { } func (t *Table) reconcile(ctx context.Context) error { + t.data.Mutex.Lock() + defer t.data.Mutex.Unlock() + log.Debug().Msgf("GOROUTINE %d", runtime.NumGoroutine()) factory, ok := ctx.Value(internal.KeyFactory).(Factory) diff --git a/internal/render/table.go b/internal/render/table.go index d8ceb542..c6c5ddf4 100644 --- a/internal/render/table.go +++ b/internal/render/table.go @@ -1,10 +1,18 @@ 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{}} } // Clear clears out the entire table. diff --git a/internal/ui/table.go b/internal/ui/table.go index 1050810a..35116ecd 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -134,6 +134,9 @@ 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() + var firstRow bool if t.GetRowCount() == 0 { firstRow = true diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go index 7f636e65..30a1bb1d 100644 --- a/internal/ui/table_test.go +++ b/internal/ui/table_test.go @@ -69,26 +69,27 @@ func (t *testModel) InNamespace(string) bool { return true } func (t *testModel) SetRefreshRate(time.Duration) {} func makeTableData() render.TableData { - return render.TableData{ - Namespace: "", - Header: render.HeaderRow{ - render.Header{Name: "a"}, - render.Header{Name: "b"}, - render.Header{Name: "c"}, - }, - RowEvents: render.RowEvents{ - render.RowEvent{ - Row: render.Row{ - ID: "r1", - Fields: render.Fields{"blee", "duh", "fred"}, - }, + t := render.NewTableData() + t.Namespace = "" + t.Header = render.HeaderRow{ + render.Header{Name: "a"}, + render.Header{Name: "b"}, + render.Header{Name: "c"}, + } + t.RowEvents = render.RowEvents{ + render.RowEvent{ + Row: render.Row{ + ID: "r1", + Fields: render.Fields{"blee", "duh", "fred"}, }, - render.RowEvent{ - Row: render.Row{ - ID: "r2", - Fields: render.Fields{"blee", "duh", "zorg"}, - }, + }, + render.RowEvent{ + Row: render.Row{ + ID: "r2", + Fields: render.Fields{"blee", "duh", "zorg"}, }, }, } + + return *t } diff --git a/internal/view/app.go b/internal/view/app.go index e94d1fdb..71ea7cc3 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -193,7 +193,9 @@ func (a *App) Resume() { var ctx context.Context ctx, a.cancelFn = context.WithCancel(context.Background()) go a.clusterUpdater(ctx) - a.StylesUpdater(ctx, a) + if err := a.StylesUpdater(ctx, a); err != nil { + log.Error().Err(err).Msgf("Styles update failed") + } } func (a *App) clusterUpdater(ctx context.Context) { diff --git a/internal/view/dp.go b/internal/view/dp.go index 48178845..ec6f169f 100644 --- a/internal/view/dp.go +++ b/internal/view/dp.go @@ -1,6 +1,8 @@ package view import ( + "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" @@ -52,7 +54,7 @@ func (d *Deploy) showPods(app *App, _, _, path string) { app.Flash().Err(err) } - showPodsFromSelector(app, path, dp.Spec.Selector) + showPodsFromSelector(app, strings.Replace(path, "/", "::", 1), dp.Spec.Selector) } // Helpers... diff --git a/internal/view/ds.go b/internal/view/ds.go index fff9e316..ad48890e 100644 --- a/internal/view/ds.go +++ b/internal/view/ds.go @@ -1,6 +1,8 @@ package view import ( + "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" @@ -47,5 +49,5 @@ func (d *DaemonSet) showPods(app *App, _, _, path string) { d.App().Flash().Err(err) } - showPodsFromSelector(app, path, ds.Spec.Selector) + showPodsFromSelector(app, strings.Replace(path, "/", "::", 1), ds.Spec.Selector) } diff --git a/internal/view/log.go b/internal/view/log.go index f8a0d8dd..2074a9d9 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -41,7 +41,6 @@ type Log struct { cancelFn context.CancelFunc previous bool gvr client.GVR - fullScreen bool } var _ model.Component = &Log{} diff --git a/internal/view/logs_extender.go b/internal/view/logs_extender.go index 655fe8f2..5a4e39e6 100644 --- a/internal/view/logs_extender.go +++ b/internal/view/logs_extender.go @@ -39,7 +39,7 @@ func (l *LogsExtender) logsCmd(prev bool) func(evt *tcell.EventKey) *tcell.Event if path == "" { return nil } - if l.GetTable().Path != "" { + if isResourcePath(l.GetTable().Path) { path = l.GetTable().Path } l.showLogs(path, prev) @@ -48,11 +48,15 @@ func (l *LogsExtender) logsCmd(prev bool) func(evt *tcell.EventKey) *tcell.Event } } +func isResourcePath(p string) bool { + ns, n := client.Namespaced(p) + return ns != "" && n != "" +} + func (l *LogsExtender) showLogs(path string, prev bool) { log.Debug().Msgf("SHOWING LOGS path %q", path) co := "" if l.containerFn != nil { - log.Debug().Msgf("CUSTOM CO FUNC") co = l.containerFn() } if err := l.App().inject(NewLog(client.GVR(l.GVR()), path, co, prev)); err != nil { diff --git a/internal/view/sts.go b/internal/view/sts.go index 1002f9de..13842ade 100644 --- a/internal/view/sts.go +++ b/internal/view/sts.go @@ -1,6 +1,8 @@ package view import ( + "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" @@ -51,5 +53,5 @@ func (s *StatefulSet) showPods(app *App, _, gvr, path string) { app.Flash().Err(err) } - showPodsFromSelector(app, path, sts.Spec.Selector) + showPodsFromSelector(app, strings.Replace(path, "/", "::", 1), sts.Spec.Selector) } diff --git a/internal/view/table_int_test.go b/internal/view/table_int_test.go index 743aee96..f5c547a8 100644 --- a/internal/view/table_int_test.go +++ b/internal/view/table_int_test.go @@ -33,28 +33,28 @@ func TestTableNew(t *testing.T) { v := NewTable("test") v.Init(makeContext()) - data := render.TableData{ - Header: render.HeaderRow{ - render.Header{Name: "NAMESPACE"}, - render.Header{Name: "NAME", Align: tview.AlignRight}, - render.Header{Name: "FRED"}, - render.Header{Name: "AGE", Decorator: render.AgeDecorator}, - }, - RowEvents: render.RowEvents{ - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"ns1", "a", "10", "3m"}, - }, - }, - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"ns1", "b", "15", "1m"}, - }, - }, - }, - Namespace: "", + data := render.NewTableData() + data.Header = render.HeaderRow{ + render.Header{Name: "NAMESPACE"}, + render.Header{Name: "NAME", Align: tview.AlignRight}, + render.Header{Name: "FRED"}, + render.Header{Name: "AGE", Decorator: render.AgeDecorator}, } - v.Update(data) + data.RowEvents = render.RowEvents{ + render.RowEvent{ + Row: render.Row{ + Fields: render.Fields{"ns1", "a", "10", "3m"}, + }, + }, + render.RowEvent{ + Row: render.Row{ + Fields: render.Fields{"ns1", "b", "15", "1m"}, + }, + }, + } + data.Namespace = "" + + v.Update(*data) assert.Equal(t, 3, v.GetRowCount()) } @@ -101,28 +101,30 @@ func (t *testTableModel) InNamespace(string) bool { return true } func (t *testTableModel) SetRefreshRate(time.Duration) {} func makeTableData() render.TableData { - return render.TableData{ - Header: render.HeaderRow{ - render.Header{Name: "NAMESPACE"}, - render.Header{Name: "NAME", Align: tview.AlignRight}, - render.Header{Name: "FRED"}, - render.Header{Name: "AGE", Decorator: render.AgeDecorator}, - }, - RowEvents: render.RowEvents{ - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"ns1", "blee", "10", "3m"}, - }, - }, - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"ns1", "fred", "15", "1m"}, - }, - Deltas: render.DeltaRow{"", "", "20", ""}, - }, - }, - Namespace: "", + t := render.NewTableData() + + t.Header = render.HeaderRow{ + render.Header{Name: "NAMESPACE"}, + render.Header{Name: "NAME", Align: tview.AlignRight}, + render.Header{Name: "FRED"}, + render.Header{Name: "AGE", Decorator: render.AgeDecorator}, } + t.RowEvents = render.RowEvents{ + render.RowEvent{ + Row: render.Row{ + Fields: render.Fields{"ns1", "blee", "10", "3m"}, + }, + }, + render.RowEvent{ + Row: render.Row{ + Fields: render.Fields{"ns1", "fred", "15", "1m"}, + }, + Deltas: render.DeltaRow{"", "", "20", ""}, + }, + } + t.Namespace = "" + + return *t } func makeContext() context.Context { diff --git a/main.go b/main.go index 2d854cff..a040a539 100644 --- a/main.go +++ b/main.go @@ -8,9 +8,6 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" _ "k8s.io/client-go/plugin/pkg/client/auth" - - "net/http" - _ "net/http/pprof" ) func init() { @@ -23,11 +20,6 @@ func main() { if err != nil { panic(err) } - - go func() { - http.ListenAndServe("localhost:6060", nil) - }() - log.Logger = log.Output(zerolog.ConsoleWriter{Out: file}) cmd.Execute()