checkpoint
parent
365fc01f17
commit
5c0fc0845b
|
|
@ -3,6 +3,7 @@ package client
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
|
@ -19,11 +20,15 @@ type Config struct {
|
||||||
currentContext string
|
currentContext string
|
||||||
rawConfig *clientcmdapi.Config
|
rawConfig *clientcmdapi.Config
|
||||||
restConfig *restclient.Config
|
restConfig *restclient.Config
|
||||||
|
mutex *sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
func NewConfig(f *genericclioptions.ConfigFlags) *Config {
|
func NewConfig(f *genericclioptions.ConfigFlags) *Config {
|
||||||
return &Config{flags: f}
|
return &Config{
|
||||||
|
flags: f,
|
||||||
|
mutex: &sync.RWMutex{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flags returns configuration flags.
|
// 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.
|
// ConfigAccess return the current kubeconfig api server access configuration.
|
||||||
func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) {
|
func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) {
|
||||||
|
c.mutex.RLock()
|
||||||
|
defer c.mutex.RUnlock()
|
||||||
|
|
||||||
c.ensureConfig()
|
c.ensureConfig()
|
||||||
return c.clientConfig.ConfigAccess(), nil
|
return c.clientConfig.ConfigAccess(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawConfig fetch the current kubeconfig with no overrides.
|
// RawConfig fetch the current kubeconfig with no overrides.
|
||||||
func (c *Config) RawConfig() (clientcmdapi.Config, error) {
|
func (c *Config) RawConfig() (clientcmdapi.Config, error) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
if c.rawConfig != nil {
|
if c.rawConfig != nil {
|
||||||
if c.rawConfig.CurrentContext == c.currentContext {
|
if c.rawConfig.CurrentContext == c.currentContext {
|
||||||
return *c.rawConfig, nil
|
return *c.rawConfig, nil
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ type TableListener interface {
|
||||||
type Table struct {
|
type Table struct {
|
||||||
gvr string
|
gvr string
|
||||||
namespace string
|
namespace string
|
||||||
data render.TableData
|
data *render.TableData
|
||||||
listeners []TableListener
|
listeners []TableListener
|
||||||
inUpdate int32
|
inUpdate int32
|
||||||
refreshRate time.Duration
|
refreshRate time.Duration
|
||||||
|
|
@ -32,7 +32,7 @@ type Table struct {
|
||||||
func NewTable(gvr string) *Table {
|
func NewTable(gvr string) *Table {
|
||||||
return &Table{
|
return &Table{
|
||||||
gvr: gvr,
|
gvr: gvr,
|
||||||
data: render.TableData{},
|
data: render.NewTableData(),
|
||||||
refreshRate: 2 * time.Second,
|
refreshRate: 2 * time.Second,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +81,7 @@ func (t *Table) Empty() bool {
|
||||||
|
|
||||||
// Peek returns model data.
|
// Peek returns model data.
|
||||||
func (t *Table) Peek() render.TableData {
|
func (t *Table) Peek() render.TableData {
|
||||||
return t.data
|
return *t.data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) updater(ctx context.Context) {
|
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")
|
log.Error().Err(err).Msg("Reconcile failed")
|
||||||
t.fireTableLoadFailed(err)
|
t.fireTableLoadFailed(err)
|
||||||
}
|
}
|
||||||
t.fireTableChanged(t.data)
|
t.fireTableChanged(*t.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddListener adds a new model listener.
|
// AddListener adds a new model listener.
|
||||||
func (t *Table) AddListener(l TableListener) {
|
func (t *Table) AddListener(l TableListener) {
|
||||||
t.listeners = append(t.listeners, l)
|
t.listeners = append(t.listeners, l)
|
||||||
t.fireTableChanged(t.data)
|
t.fireTableChanged(*t.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveListener delete a listener from the list.
|
// 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 {
|
func (t *Table) reconcile(ctx context.Context) error {
|
||||||
|
t.data.Mutex.Lock()
|
||||||
|
defer t.data.Mutex.Unlock()
|
||||||
|
|
||||||
log.Debug().Msgf("GOROUTINE %d", runtime.NumGoroutine())
|
log.Debug().Msgf("GOROUTINE %d", runtime.NumGoroutine())
|
||||||
|
|
||||||
factory, ok := ctx.Value(internal.KeyFactory).(Factory)
|
factory, ok := ctx.Value(internal.KeyFactory).(Factory)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
package render
|
package render
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
// TableData tracks a K8s resource for tabular display.
|
// TableData tracks a K8s resource for tabular display.
|
||||||
type TableData struct {
|
type TableData struct {
|
||||||
Header HeaderRow
|
Header HeaderRow
|
||||||
RowEvents RowEvents
|
RowEvents RowEvents
|
||||||
Namespace string
|
Namespace string
|
||||||
|
Mutex *sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTableData returns a new table.
|
||||||
|
func NewTableData() *TableData {
|
||||||
|
return &TableData{Mutex: &sync.RWMutex{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear clears out the entire table.
|
// Clear clears out the entire table.
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,9 @@ func (t *Table) SetSortCol(index, count int, asc bool) {
|
||||||
|
|
||||||
// Update table content.
|
// Update table content.
|
||||||
func (t *Table) Update(data render.TableData) {
|
func (t *Table) Update(data render.TableData) {
|
||||||
|
data.Mutex.RLock()
|
||||||
|
defer data.Mutex.RUnlock()
|
||||||
|
|
||||||
var firstRow bool
|
var firstRow bool
|
||||||
if t.GetRowCount() == 0 {
|
if t.GetRowCount() == 0 {
|
||||||
firstRow = true
|
firstRow = true
|
||||||
|
|
|
||||||
|
|
@ -69,14 +69,14 @@ func (t *testModel) InNamespace(string) bool { return true }
|
||||||
func (t *testModel) SetRefreshRate(time.Duration) {}
|
func (t *testModel) SetRefreshRate(time.Duration) {}
|
||||||
|
|
||||||
func makeTableData() render.TableData {
|
func makeTableData() render.TableData {
|
||||||
return render.TableData{
|
t := render.NewTableData()
|
||||||
Namespace: "",
|
t.Namespace = ""
|
||||||
Header: render.HeaderRow{
|
t.Header = render.HeaderRow{
|
||||||
render.Header{Name: "a"},
|
render.Header{Name: "a"},
|
||||||
render.Header{Name: "b"},
|
render.Header{Name: "b"},
|
||||||
render.Header{Name: "c"},
|
render.Header{Name: "c"},
|
||||||
},
|
}
|
||||||
RowEvents: render.RowEvents{
|
t.RowEvents = render.RowEvents{
|
||||||
render.RowEvent{
|
render.RowEvent{
|
||||||
Row: render.Row{
|
Row: render.Row{
|
||||||
ID: "r1",
|
ID: "r1",
|
||||||
|
|
@ -89,6 +89,7 @@ func makeTableData() render.TableData {
|
||||||
Fields: render.Fields{"blee", "duh", "zorg"},
|
Fields: render.Fields{"blee", "duh", "zorg"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return *t
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,9 @@ func (a *App) Resume() {
|
||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
ctx, a.cancelFn = context.WithCancel(context.Background())
|
ctx, a.cancelFn = context.WithCancel(context.Background())
|
||||||
go a.clusterUpdater(ctx)
|
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) {
|
func (a *App) clusterUpdater(ctx context.Context) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package view
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
|
|
@ -52,7 +54,7 @@ func (d *Deploy) showPods(app *App, _, _, path string) {
|
||||||
app.Flash().Err(err)
|
app.Flash().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
showPodsFromSelector(app, path, dp.Spec.Selector)
|
showPodsFromSelector(app, strings.Replace(path, "/", "::", 1), dp.Spec.Selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package view
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
|
|
@ -47,5 +49,5 @@ func (d *DaemonSet) showPods(app *App, _, _, path string) {
|
||||||
d.App().Flash().Err(err)
|
d.App().Flash().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
showPodsFromSelector(app, path, ds.Spec.Selector)
|
showPodsFromSelector(app, strings.Replace(path, "/", "::", 1), ds.Spec.Selector)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ type Log struct {
|
||||||
cancelFn context.CancelFunc
|
cancelFn context.CancelFunc
|
||||||
previous bool
|
previous bool
|
||||||
gvr client.GVR
|
gvr client.GVR
|
||||||
fullScreen bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ model.Component = &Log{}
|
var _ model.Component = &Log{}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ func (l *LogsExtender) logsCmd(prev bool) func(evt *tcell.EventKey) *tcell.Event
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if l.GetTable().Path != "" {
|
if isResourcePath(l.GetTable().Path) {
|
||||||
path = l.GetTable().Path
|
path = l.GetTable().Path
|
||||||
}
|
}
|
||||||
l.showLogs(path, prev)
|
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) {
|
func (l *LogsExtender) showLogs(path string, prev bool) {
|
||||||
log.Debug().Msgf("SHOWING LOGS path %q", path)
|
log.Debug().Msgf("SHOWING LOGS path %q", path)
|
||||||
co := ""
|
co := ""
|
||||||
if l.containerFn != nil {
|
if l.containerFn != nil {
|
||||||
log.Debug().Msgf("CUSTOM CO FUNC")
|
|
||||||
co = l.containerFn()
|
co = l.containerFn()
|
||||||
}
|
}
|
||||||
if err := l.App().inject(NewLog(client.GVR(l.GVR()), path, co, prev)); err != nil {
|
if err := l.App().inject(NewLog(client.GVR(l.GVR()), path, co, prev)); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package view
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
|
|
@ -51,5 +53,5 @@ func (s *StatefulSet) showPods(app *App, _, gvr, path string) {
|
||||||
app.Flash().Err(err)
|
app.Flash().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
showPodsFromSelector(app, path, sts.Spec.Selector)
|
showPodsFromSelector(app, strings.Replace(path, "/", "::", 1), sts.Spec.Selector)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,14 +33,14 @@ func TestTableNew(t *testing.T) {
|
||||||
v := NewTable("test")
|
v := NewTable("test")
|
||||||
v.Init(makeContext())
|
v.Init(makeContext())
|
||||||
|
|
||||||
data := render.TableData{
|
data := render.NewTableData()
|
||||||
Header: render.HeaderRow{
|
data.Header = render.HeaderRow{
|
||||||
render.Header{Name: "NAMESPACE"},
|
render.Header{Name: "NAMESPACE"},
|
||||||
render.Header{Name: "NAME", Align: tview.AlignRight},
|
render.Header{Name: "NAME", Align: tview.AlignRight},
|
||||||
render.Header{Name: "FRED"},
|
render.Header{Name: "FRED"},
|
||||||
render.Header{Name: "AGE", Decorator: render.AgeDecorator},
|
render.Header{Name: "AGE", Decorator: render.AgeDecorator},
|
||||||
},
|
}
|
||||||
RowEvents: render.RowEvents{
|
data.RowEvents = render.RowEvents{
|
||||||
render.RowEvent{
|
render.RowEvent{
|
||||||
Row: render.Row{
|
Row: render.Row{
|
||||||
Fields: render.Fields{"ns1", "a", "10", "3m"},
|
Fields: render.Fields{"ns1", "a", "10", "3m"},
|
||||||
|
|
@ -51,10 +51,10 @@ func TestTableNew(t *testing.T) {
|
||||||
Fields: render.Fields{"ns1", "b", "15", "1m"},
|
Fields: render.Fields{"ns1", "b", "15", "1m"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
Namespace: "",
|
|
||||||
}
|
}
|
||||||
v.Update(data)
|
data.Namespace = ""
|
||||||
|
|
||||||
|
v.Update(*data)
|
||||||
assert.Equal(t, 3, v.GetRowCount())
|
assert.Equal(t, 3, v.GetRowCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,14 +101,15 @@ func (t *testTableModel) InNamespace(string) bool { return true }
|
||||||
func (t *testTableModel) SetRefreshRate(time.Duration) {}
|
func (t *testTableModel) SetRefreshRate(time.Duration) {}
|
||||||
|
|
||||||
func makeTableData() render.TableData {
|
func makeTableData() render.TableData {
|
||||||
return render.TableData{
|
t := render.NewTableData()
|
||||||
Header: render.HeaderRow{
|
|
||||||
|
t.Header = render.HeaderRow{
|
||||||
render.Header{Name: "NAMESPACE"},
|
render.Header{Name: "NAMESPACE"},
|
||||||
render.Header{Name: "NAME", Align: tview.AlignRight},
|
render.Header{Name: "NAME", Align: tview.AlignRight},
|
||||||
render.Header{Name: "FRED"},
|
render.Header{Name: "FRED"},
|
||||||
render.Header{Name: "AGE", Decorator: render.AgeDecorator},
|
render.Header{Name: "AGE", Decorator: render.AgeDecorator},
|
||||||
},
|
}
|
||||||
RowEvents: render.RowEvents{
|
t.RowEvents = render.RowEvents{
|
||||||
render.RowEvent{
|
render.RowEvent{
|
||||||
Row: render.Row{
|
Row: render.Row{
|
||||||
Fields: render.Fields{"ns1", "blee", "10", "3m"},
|
Fields: render.Fields{"ns1", "blee", "10", "3m"},
|
||||||
|
|
@ -120,9 +121,10 @@ func makeTableData() render.TableData {
|
||||||
},
|
},
|
||||||
Deltas: render.DeltaRow{"", "", "20", ""},
|
Deltas: render.DeltaRow{"", "", "20", ""},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
Namespace: "",
|
|
||||||
}
|
}
|
||||||
|
t.Namespace = ""
|
||||||
|
|
||||||
|
return *t
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeContext() context.Context {
|
func makeContext() context.Context {
|
||||||
|
|
|
||||||
8
main.go
8
main.go
|
|
@ -8,9 +8,6 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||||
|
|
||||||
"net/http"
|
|
||||||
_ "net/http/pprof"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -23,11 +20,6 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
|
||||||
http.ListenAndServe("localhost:6060", nil)
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: file})
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: file})
|
||||||
|
|
||||||
cmd.Execute()
|
cmd.Execute()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue