derailed 2020-05-01 12:01:21 -06:00
parent f0b56964ea
commit 56a520c2d1
18 changed files with 154 additions and 50 deletions

View File

@ -45,7 +45,7 @@ type APIClient struct {
config *Config config *Config
mx sync.Mutex mx sync.Mutex
cache *cache.LRUExpireCache cache *cache.LRUExpireCache
metricsAPI bool connOK bool
} }
// NewTestClient for testing ONLY!! // NewTestClient for testing ONLY!!
@ -63,7 +63,7 @@ func InitConnectionOrDie(config *Config) *APIClient {
config: config, config: config,
cache: cache.NewLRUExpireCache(cacheSize), cache: cache.NewLRUExpireCache(cacheSize),
} }
a.metricsAPI = a.supportsMetricsResources() _ = a.supportsMetricsResources()
return &a return &a
} }
@ -168,12 +168,21 @@ func (a *APIClient) ServerVersion() (*version.Info, error) {
// ValidNamespaces returns all available namespaces. // ValidNamespaces returns all available namespaces.
func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) { func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
if nn, ok := a.cache.Get("validNamespaces"); ok {
if nss, ok := nn.([]v1.Namespace); ok {
return nss, nil
}
}
log.Debug().Msgf(">>>>> Loading all namespaces")
ctx, cancel := context.WithTimeout(context.Background(), CallTimeout) ctx, cancel := context.WithTimeout(context.Background(), CallTimeout)
defer cancel() defer cancel()
nn, err := a.DialOrDie().CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) nn, err := a.DialOrDie().CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
a.cache.Add("validNamespaces", nn.Items, cacheExpiry)
return nn.Items, nil return nn.Items, nil
} }
@ -186,6 +195,7 @@ func (a *APIClient) CheckConnectivity() (status bool) {
if !status { if !status {
a.clearCache() a.clearCache()
} }
a.connOK = status
}() }()
cfg, err := a.config.flags.ToRESTConfig() cfg, err := a.config.flags.ToRESTConfig()
@ -201,7 +211,10 @@ func (a *APIClient) CheckConnectivity() (status bool) {
} }
if _, err := client.ServerVersion(); err == nil { if _, err := client.ServerVersion(); err == nil {
if !a.connOK {
log.Debug().Msgf("RESETING CON!!")
a.reset() a.reset()
}
status = true status = true
} else { } else {
log.Error().Err(err).Msgf("K9s can't connect to cluster") log.Error().Err(err).Msgf("K9s can't connect to cluster")
@ -332,7 +345,7 @@ func (a *APIClient) SwitchContext(ctx string) error {
} }
a.clearCache() a.clearCache()
a.reset() a.reset()
a.metricsAPI = a.supportsMetricsResources() _ = a.supportsMetricsResources()
ResetMetrics() ResetMetrics()
return nil return nil

View File

@ -1,6 +1,8 @@
package config package config
import "github.com/derailed/k9s/internal/client" import (
"github.com/derailed/k9s/internal/client"
)
// Cluster tracks K9s cluster configuration. // Cluster tracks K9s cluster configuration.
type Cluster struct { type Cluster struct {
@ -23,7 +25,6 @@ func (c *Cluster) Validate(conn client.Connection, ks KubeSettings) {
if c.Namespace == nil { if c.Namespace == nil {
c.Namespace = NewNamespace() c.Namespace = NewNamespace()
} }
c.Namespace.Validate(conn, ks)
if c.FeatureGates == nil { if c.FeatureGates == nil {
c.FeatureGates = NewFeatureGates() c.FeatureGates = NewFeatureGates()

View File

@ -132,13 +132,22 @@ func (c *Config) ActiveNamespace() string {
return "default" return "default"
} }
func (c *Config) ValidateFavorites() {
cl := c.K9s.ActiveCluster()
if cl == nil {
cl = NewCluster()
}
cl.Validate(c.client, c.settings)
cl.Namespace.Validate(c.client, c.settings)
}
// FavNamespaces returns fav namespaces in the current cluster. // FavNamespaces returns fav namespaces in the current cluster.
func (c *Config) FavNamespaces() []string { func (c *Config) FavNamespaces() []string {
cl := c.K9s.ActiveCluster() cl := c.K9s.ActiveCluster()
if cl != nil { if cl == nil {
return c.K9s.ActiveCluster().Namespace.Favorites return nil
} }
return []string{} return c.K9s.ActiveCluster().Namespace.Favorites
} }
// SetActiveNamespace set the active namespace in the current cluster. // SetActiveNamespace set the active namespace in the current cluster.

View File

@ -288,8 +288,10 @@ var expectedConfig = `k9s:
active: default active: default
favorites: favorites:
- default - default
- kube-public
- istio-system - istio-system
- all - all
- kube-system
view: view:
active: po active: po
featureGates: featureGates:
@ -299,8 +301,10 @@ var expectedConfig = `k9s:
active: kube-system active: kube-system
favorites: favorites:
- default - default
- kube-public
- istio-system - istio-system
- all - all
- kube-system
view: view:
active: ctx active: ctx
featureGates: featureGates:

View File

@ -100,7 +100,7 @@ func (k *K9s) validateDefaults() {
} }
} }
func (k *K9s) checkClusters(c client.Connection, ks KubeSettings) { func (k *K9s) validateClusters(c client.Connection, ks KubeSettings) {
cc, err := ks.ClusterNames() cc, err := ks.ClusterNames()
if err != nil { if err != nil {
return return
@ -123,7 +123,7 @@ func (k *K9s) Validate(c client.Connection, ks KubeSettings) {
if k.Clusters == nil { if k.Clusters == nil {
k.Clusters = map[string]*Cluster{} k.Clusters = map[string]*Cluster{}
} }
k.checkClusters(c, ks) k.validateClusters(c, ks)
if k.Logger == nil { if k.Logger == nil {
k.Logger = NewLogger() k.Logger = NewLogger()

View File

@ -59,7 +59,6 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
// TailLogs tails a given container logs // TailLogs tails a given container logs
func (c *Container) TailLogs(ctx context.Context, logChan LogChan, opts LogOptions) error { func (c *Container) TailLogs(ctx context.Context, logChan LogChan, opts LogOptions) error {
log.Debug().Msgf("CONTAINER-LOGS")
po := Pod{} po := Pod{}
po.Init(c.Factory, client.NewGVR("v1/pods")) po.Init(c.Factory, client.NewGVR("v1/pods"))

View File

@ -19,6 +19,7 @@ type LogChan chan *LogItem
// LogItem represents a container log line. // LogItem represents a container log line.
type LogItem struct { type LogItem struct {
Pod, Container, Timestamp string Pod, Container, Timestamp string
SingleContainer bool
Bytes []byte Bytes []byte
} }
@ -78,7 +79,7 @@ func (l *LogItem) Render(c int, showTime bool) []byte {
bb = append(bb, []byte(colorize(l.Pod, c))...) bb = append(bb, []byte(colorize(l.Pod, c))...)
bb = append(bb, ':') bb = append(bb, ':')
} }
if l.Container != "" { if !l.SingleContainer && l.Container != "" {
bb = append(bb, []byte(colorize(l.Container, c))...) bb = append(bb, []byte(colorize(l.Container, c))...)
bb = append(bb, ' ') bb = append(bb, ' ')
} }

View File

@ -82,7 +82,10 @@ func (o LogOptions) DecorateLog(bytes []byte) *LogItem {
if len(bytes) == 0 { if len(bytes) == 0 {
return item return item
} }
item.SingleContainer = o.SingleContainer
if item.SingleContainer {
item.Container = o.Container
}
if o.MultiPods { if o.MultiPods {
_, pod := client.Namespaced(o.Path) _, pod := client.Namespaced(o.Path)
item.Pod, item.Container = pod, o.Container item.Pod, item.Container = pod, o.Container

View File

@ -187,10 +187,7 @@ func (p *Pod) TailLogs(ctx context.Context, c LogChan, opts LogOptions) error {
if opts.HasContainer() { if opts.HasContainer() {
opts.SingleContainer = true opts.SingleContainer = true
if err := tailLogs(ctx, p, c, opts); err != nil { return tailLogs(ctx, p, c, opts)
return err
}
return nil
} }
if len(po.Spec.InitContainers)+len(po.Spec.Containers) == 1 { if len(po.Spec.InitContainers)+len(po.Spec.Containers) == 1 {
opts.SingleContainer = true opts.SingleContainer = true

View File

@ -74,10 +74,14 @@ func (l *Log) Configure(opts *config.Logger) {
} }
// GetPath returns resource path. // GetPath returns resource path.
func (l *Log) GetPath() string { return l.logOptions.Path } func (l *Log) GetPath() string {
return l.logOptions.Path
}
// GetContainer returns the resource container if any or "" otherwise. // GetContainer returns the resource container if any or "" otherwise.
func (l *Log) GetContainer() string { return l.logOptions.Container } func (l *Log) GetContainer() string {
return l.logOptions.Container
}
// Init initializes the model. // Init initializes the model.
func (l *Log) Init(f dao.Factory) { func (l *Log) Init(f dao.Factory) {

View File

@ -132,7 +132,7 @@ func (a *App) initSignals() {
go func(sig chan os.Signal) { go func(sig chan os.Signal) {
<-sig <-sig
a.BailOut() nukeK9sShell(a.Conn())
}(sig) }(sig)
} }
@ -296,18 +296,36 @@ func (a *App) refreshCluster() {
a.clusterModel.Refresh() a.clusterModel.Refresh()
} }
func (a *App) switchNS(ns string) bool { func (a *App) switchNS(ns string) error {
if ns == client.ClusterScope { if ns == client.ClusterScope {
ns = client.AllNamespaces ns = client.AllNamespaces
} }
if !a.isValidNS(ns) {
return fmt.Errorf("Invalid namespace %q", ns)
}
if err := a.Config.SetActiveNamespace(ns); err != nil { if err := a.Config.SetActiveNamespace(ns); err != nil {
log.Error().Err(err).Msg("Config Set NS failed!") return fmt.Errorf("Unable to save active namespace in config")
return false
} }
a.factory.SetActiveNS(ns) a.factory.SetActiveNS(ns)
return nil
}
func (a *App) isValidNS(ns string) bool {
if ns == client.AllNamespaces || ns == client.NamespaceAll {
return true return true
} }
nn, err := a.Conn().ValidNamespaces()
if err != nil {
return false
}
for _, n := range nn {
if n.Name == ns {
return true
}
}
return false
}
func (a *App) switchCtx(name string, loadPods bool) error { func (a *App) switchCtx(name string, loadPods bool) error {
log.Debug().Msgf("--> Switching Context %q--%q", name, a.Config.ActiveView()) log.Debug().Msgf("--> Switching Context %q--%q", name, a.Config.ActiveView())
@ -323,10 +341,11 @@ func (a *App) switchCtx(name string, loadPods bool) error {
if err := a.command.Reset(true); err != nil { if err := a.command.Reset(true); err != nil {
return err return err
} }
a.Config.Reset()
if err := a.Config.Save(); err != nil { if err := a.Config.Save(); err != nil {
log.Error().Err(err).Msg("Config save failed!") log.Error().Err(err).Msg("Config save failed!")
} }
a.Config.Reset()
a.Flash().Infof("Switching context to %s", name) a.Flash().Infof("Switching context to %s", name)
a.ReloadStyles(name) a.ReloadStyles(name)
v := a.Config.ActiveView() v := a.Config.ActiveView()

View File

@ -120,6 +120,11 @@ func (b *Browser) SetInstance(path string) {
// Start initializes browser updates. // Start initializes browser updates.
func (b *Browser) Start() { func (b *Browser) Start() {
b.app.Config.ValidateFavorites()
if err := b.app.Config.Save(); err != nil {
log.Error().Err(err).Msgf("Config Save")
}
b.Stop() b.Stop()
b.Table.Start() b.Table.Start()
b.CmdBuff().AddListener(b) b.CmdBuff().AddListener(b)
@ -359,7 +364,10 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil return nil
} }
b.app.switchNS(ns) if err := b.app.switchNS(ns); err != nil {
b.App().Flash().Err(err)
return nil
}
b.setNamespace(ns) b.setNamespace(ns)
b.app.Flash().Infof("Viewing namespace `%s`...", ns) b.app.Flash().Infof("Viewing namespace `%s`...", ns)
b.refresh() b.refresh()

View File

@ -147,8 +147,8 @@ func (c *Command) run(cmd, path string, clearStack bool) error {
if len(cmds) == 2 { if len(cmds) == 2 {
ns = cmds[1] ns = cmds[1]
} }
if !c.app.switchNS(ns) { if err := c.app.switchNS(ns); err != nil {
return fmt.Errorf("namespace switch failed for ns %q", ns) return err
} }
if !c.alias.Check(cmds[0]) { if !c.alias.Check(cmds[0]) {
return fmt.Errorf("Huh? `%s` Command not found", cmd) return fmt.Errorf("Huh? `%s` Command not found", cmd)
@ -159,7 +159,7 @@ func (c *Command) run(cmd, path string, clearStack bool) error {
func (c *Command) defaultCmd() error { func (c *Command) defaultCmd() error {
if err := c.run(c.app.Config.ActiveView(), "", true); err != nil { if err := c.run(c.app.Config.ActiveView(), "", true); err != nil {
log.Error().Err(err).Msgf("Saved command failed. Loading default view") log.Error().Err(err).Msgf("Saved command load failed. Loading default view")
return c.run("pod", "", true) return c.run("pod", "", true)
} }
return nil return nil
@ -228,7 +228,22 @@ func (c *Command) componentFor(gvr, path string, v *MetaViewer) ResourceViewer {
return view return view
} }
func (c *Command) exec(cmd, gvr string, comp model.Component, clearStack bool) error { func (c *Command) exec(cmd, gvr string, comp model.Component, clearStack bool) (err error) {
defer func() {
if e := recover(); e != nil {
c.app.Content.Dump()
log.Debug().Msgf("History %v", c.app.cmdHistory.List())
hh := c.app.cmdHistory.List()
if len(hh) == 0 {
_ = c.run("pod", "", true)
} else {
_ = c.run(hh[0], "", true)
}
err = fmt.Errorf("Invalid command %q", cmd)
}
}()
if comp == nil { if comp == nil {
return fmt.Errorf("No component found for %s", gvr) return fmt.Errorf("No component found for %s", gvr)
} }
@ -246,5 +261,5 @@ func (c *Command) exec(cmd, gvr string, comp model.Component, clearStack bool) e
} }
c.app.cmdHistory.Push(cmd) c.app.cmdHistory.Push(cmd)
return nil return
} }

View File

@ -84,7 +84,10 @@ func showPodsWithLabels(app *App, path string, sel map[string]string) {
} }
func showPods(app *App, path, labelSel, fieldSel string) { func showPods(app *App, path, labelSel, fieldSel string) {
app.switchNS(client.AllNamespaces) if err := app.switchNS(client.AllNamespaces); err != nil {
app.Flash().Err(err)
return
}
v := NewPod(client.NewGVR("v1/pods")) v := NewPod(client.NewGVR("v1/pods"))
v.SetContextFn(podCtx(app, path, labelSel, fieldSel)) v.SetContextFn(podCtx(app, path, labelSel, fieldSel))

View File

@ -59,13 +59,16 @@ func (n *Namespace) useNsCmd(evt *tcell.EventKey) *tcell.EventKey {
func (n *Namespace) useNamespace(fqn string) { func (n *Namespace) useNamespace(fqn string) {
_, ns := client.Namespaced(fqn) _, ns := client.Namespaced(fqn)
log.Debug().Msgf("SWITCHING NS %q", ns) if err := n.App().switchNS(ns); err != nil {
n.App().switchNS(ns) n.App().Flash().Err(err)
return
}
if err := n.App().Config.SetActiveNamespace(ns); err != nil { if err := n.App().Config.SetActiveNamespace(ns); err != nil {
n.App().Flash().Err(err) n.App().Flash().Err(err)
} else { return
n.App().Flash().Infof("Namespace %s is now active!", ns)
} }
n.App().Flash().Infof("Namespace %s is now active!", ns)
if err := n.App().Config.Save(); err != nil { if err := n.App().Config.Save(); err != nil {
log.Error().Err(err).Msg("Config file save failed!") log.Error().Err(err).Msg("Config file save failed!")
} }

View File

@ -2,6 +2,7 @@ package view
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
@ -72,15 +73,17 @@ func DismissPortForwards(v ResourceViewer, p *ui.Pages) {
// Helpers... // Helpers...
func extractPort(p string) string { func extractPort(p string) string {
tokens := strings.Split(p, ":") rx := regexp.MustCompile(`\A(\w+)/?(\w+)?:?(\d+)?(UDP)?\z`)
switch { mm := rx.FindStringSubmatch(p)
case len(tokens) < 2: if len(mm) != 5 {
return tokens[0] return p
case len(tokens) == 2:
return strings.Replace(tokens[1], "UDP", "", 1)
default:
return tokens[1]
} }
for i := 3; i > 0; i-- {
if mm[i] != "" {
return mm[i]
}
}
return p
} }
func extractContainer(p string) string { func extractContainer(p string) string {

View File

@ -10,6 +10,9 @@ func TestExtractPort(t *testing.T) {
uu := map[string]struct { uu := map[string]struct {
port, e string port, e string
}{ }{
"empty": {
"", "",
},
"full": { "full": {
"co/fred:8000", "8000", "co/fred:8000", "8000",
}, },
@ -22,6 +25,9 @@ func TestExtractPort(t *testing.T) {
"protocol": { "protocol": {
"dns:53UDP", "53", "dns:53UDP", "53",
}, },
"unamed": {
"dns/53", "53",
},
} }
for k := range uu { for k := range uu {

View File

@ -27,11 +27,10 @@ type Pod struct {
// NewPod returns a new viewer. // NewPod returns a new viewer.
func NewPod(gvr client.GVR) ResourceViewer { func NewPod(gvr client.GVR) ResourceViewer {
p := Pod{ p := Pod{}
ResourceViewer: NewPortForwardExtender( p.ResourceViewer = NewPortForwardExtender(
NewLogsExtender(NewBrowser(gvr), nil), NewLogsExtender(NewBrowser(gvr), p.selectedContainer),
), )
}
p.SetBindKeysFn(p.bindKeys) p.SetBindKeysFn(p.bindKeys)
p.GetTable().SetEnterFn(p.showContainers) p.GetTable().SetEnterFn(p.showContainers)
p.GetTable().SetColorerFn(render.Pod{}.ColorerFunc()) p.GetTable().SetColorerFn(render.Pod{}.ColorerFunc())
@ -67,6 +66,23 @@ func (p *Pod) bindKeys(aa ui.KeyActions) {
}) })
} }
func (p *Pod) selectedContainer() string {
path := p.GetTable().GetSelectedItem()
if path == "" {
return ""
}
cc, err := fetchContainers(p.App().factory, path, true)
if err != nil {
log.Error().Err(err).Msgf("Fetch containers")
return ""
}
if len(cc) == 1 {
return cc[0]
}
return ""
}
func (p *Pod) showContainers(app *App, model ui.Tabular, gvr, path string) { func (p *Pod) showContainers(app *App, model ui.Tabular, gvr, path string) {
co := NewContainer(client.NewGVR("containers")) co := NewContainer(client.NewGVR("containers"))
co.SetContextFn(p.coContext) co.SetContextFn(p.coContext)