Allow k9s to start without a valid Kubernetes context (#3704)
* allow initialization without a valid connection * fix: handle nil factory and alias in command and context methodsmine
parent
f27846ce23
commit
381ca5ee9e
|
|
@ -113,6 +113,11 @@ func gotoCmd(r Runner, cmd, path string, clearStack bool) ui.ActionHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func pluginActions(r Runner, aa *ui.KeyActions) error {
|
func pluginActions(r Runner, aa *ui.KeyActions) error {
|
||||||
|
// Skip plugin loading if no valid connection
|
||||||
|
if r.App().Conn() == nil || !r.App().Conn().ConnectionOK() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
aa.Range(func(k tcell.Key, a ui.KeyAction) {
|
aa.Range(func(k tcell.Key, a ui.KeyAction) {
|
||||||
if a.Opts.Plugin {
|
if a.Opts.Plugin {
|
||||||
aa.Delete(k)
|
aa.Delete(k)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"maps"
|
"maps"
|
||||||
|
|
@ -106,11 +105,11 @@ func (a *App) Init(version string, _ int) error {
|
||||||
a.App.Init()
|
a.App.Init()
|
||||||
a.SetInputCapture(a.keyboard)
|
a.SetInputCapture(a.keyboard)
|
||||||
a.bindKeys()
|
a.bindKeys()
|
||||||
if a.Conn() == nil {
|
|
||||||
return errors.New("no client connection detected")
|
|
||||||
}
|
|
||||||
ns := a.Config.ActiveNamespace()
|
|
||||||
|
|
||||||
|
// Allow initialization even without a valid connection
|
||||||
|
// We'll fall back to context view in defaultCmd
|
||||||
|
if a.Conn() != nil {
|
||||||
|
ns := a.Config.ActiveNamespace()
|
||||||
a.factory = watch.NewFactory(a.Conn())
|
a.factory = watch.NewFactory(a.Conn())
|
||||||
a.initFactory(ns)
|
a.initFactory(ns)
|
||||||
|
|
||||||
|
|
@ -121,6 +120,7 @@ func (a *App) Init(version string, _ int) error {
|
||||||
a.clusterModel.Refresh()
|
a.clusterModel.Refresh()
|
||||||
a.clusterInfo().Init()
|
a.clusterInfo().Init()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a.command = NewCommand(a)
|
a.command = NewCommand(a)
|
||||||
if err := a.command.Init(a.Config.ContextAliasesPath()); err != nil {
|
if err := a.command.Init(a.Config.ContextAliasesPath()); err != nil {
|
||||||
|
|
@ -223,6 +223,10 @@ func (a *App) suggestCommand() model.SuggestionFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) contextNames() ([]string, error) {
|
func (a *App) contextNames() ([]string, error) {
|
||||||
|
// Return empty list if no factory
|
||||||
|
if a.factory == nil {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
contexts, err := a.factory.Client().Config().Contexts()
|
contexts, err := a.factory.Client().Config().Contexts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -303,7 +307,7 @@ func (a *App) buildHeader() tview.Primitive {
|
||||||
}
|
}
|
||||||
|
|
||||||
clWidth := clusterInfoWidth
|
clWidth := clusterInfoWidth
|
||||||
if a.Conn().ConnectionOK() {
|
if a.Conn() != nil && a.Conn().ConnectionOK() {
|
||||||
n, err := a.Conn().Config().CurrentClusterName()
|
n, err := a.Conn().Config().CurrentClusterName()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
size := len(n) + clusterInfoPad
|
size := len(n) + clusterInfoPad
|
||||||
|
|
@ -351,6 +355,11 @@ func (a *App) Resume() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) clusterUpdater(ctx context.Context) {
|
func (a *App) clusterUpdater(ctx context.Context) {
|
||||||
|
if a.Conn() == nil || !a.Conn().ConnectionOK() || a.factory == nil || a.clusterModel == nil {
|
||||||
|
slog.Debug("Skipping cluster updater - no valid connection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := a.refreshCluster(ctx); err != nil {
|
if err := a.refreshCluster(ctx); err != nil {
|
||||||
slog.Error("Cluster updater failed!", slogs.Error, err)
|
slog.Error("Cluster updater failed!", slogs.Error, err)
|
||||||
return
|
return
|
||||||
|
|
@ -379,6 +388,10 @@ func (a *App) clusterUpdater(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) refreshCluster(context.Context) error {
|
func (a *App) refreshCluster(context.Context) error {
|
||||||
|
if a.Conn() == nil || a.factory == nil || a.clusterModel == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
c := a.Content.Top()
|
c := a.Content.Top()
|
||||||
if ok := a.Conn().CheckConnectivity(); ok {
|
if ok := a.Conn().CheckConnectivity(); ok {
|
||||||
if atomic.LoadInt32(&a.conRetry) > 0 {
|
if atomic.LoadInt32(&a.conRetry) > 0 {
|
||||||
|
|
@ -474,7 +487,18 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error {
|
||||||
if err := a.Config.Save(true); err != nil {
|
if err := a.Config.Save(true); err != nil {
|
||||||
slog.Error("Fail to save config to disk", slogs.Subsys, "config", slogs.Error, err)
|
slog.Error("Fail to save config to disk", slogs.Subsys, "config", slogs.Error, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.factory == nil && a.Conn() != nil {
|
||||||
|
a.factory = watch.NewFactory(a.Conn())
|
||||||
|
a.clusterModel = model.NewClusterInfo(a.factory, a.version, a.Config.K9s)
|
||||||
|
a.clusterModel.AddListener(a.clusterInfo())
|
||||||
|
a.clusterModel.AddListener(a.statusIndicator())
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.factory != nil {
|
||||||
a.initFactory(ns)
|
a.initFactory(ns)
|
||||||
|
}
|
||||||
|
|
||||||
if err := a.command.Reset(a.Config.ContextAliasesPath(), true); err != nil {
|
if err := a.command.Reset(a.Config.ContextAliasesPath(), true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -487,8 +511,11 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error {
|
||||||
a.Flash().Infof("Switching context to %q::%q", contextName, ns)
|
a.Flash().Infof("Switching context to %q::%q", contextName, ns)
|
||||||
a.ReloadStyles()
|
a.ReloadStyles()
|
||||||
a.gotoResource(a.Config.ActiveView(), "", true, true)
|
a.gotoResource(a.Config.ActiveView(), "", true, true)
|
||||||
|
|
||||||
|
if a.clusterModel != nil {
|
||||||
a.clusterModel.Reset(a.factory)
|
a.clusterModel.Reset(a.factory)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,16 +47,21 @@ func NewCommand(app *App) *Command {
|
||||||
|
|
||||||
// AliasesFor gather all known aliases for a given resource.
|
// AliasesFor gather all known aliases for a given resource.
|
||||||
func (c *Command) AliasesFor(gvr *client.GVR) sets.Set[string] {
|
func (c *Command) AliasesFor(gvr *client.GVR) sets.Set[string] {
|
||||||
|
if c.alias == nil {
|
||||||
|
return sets.New[string]()
|
||||||
|
}
|
||||||
return c.alias.AliasesFor(gvr)
|
return c.alias.AliasesFor(gvr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes the command.
|
// Init initializes the command.
|
||||||
func (c *Command) Init(path string) error {
|
func (c *Command) Init(path string) error {
|
||||||
|
if c.app.factory != nil {
|
||||||
c.alias = dao.NewAlias(c.app.factory)
|
c.alias = dao.NewAlias(c.app.factory)
|
||||||
if _, err := c.alias.Ensure(path); err != nil {
|
if _, err := c.alias.Ensure(path); err != nil {
|
||||||
slog.Error("Ensure aliases failed", slogs.Error, err)
|
slog.Error("Ensure aliases failed", slogs.Error, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
customViewers = loadCustomViewers()
|
customViewers = loadCustomViewers()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -67,6 +72,10 @@ func (c *Command) Reset(path string, nuke bool) error {
|
||||||
c.mx.Lock()
|
c.mx.Lock()
|
||||||
defer c.mx.Unlock()
|
defer c.mx.Unlock()
|
||||||
|
|
||||||
|
if c.alias == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if nuke {
|
if nuke {
|
||||||
c.alias.Clear()
|
c.alias.Clear()
|
||||||
}
|
}
|
||||||
|
|
@ -139,6 +148,9 @@ func (c *Command) xrayCmd(p *cmd.Interpreter, pushCmd bool) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("invalid command. use `xray xxx`")
|
return errors.New("invalid command. use `xray xxx`")
|
||||||
}
|
}
|
||||||
|
if c.alias == nil {
|
||||||
|
return fmt.Errorf("no connection available")
|
||||||
|
}
|
||||||
gvr, ok := c.alias.Resolve(cmd.NewInterpreter(arg))
|
gvr, ok := c.alias.Resolve(cmd.NewInterpreter(arg))
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("invalid resource name: %q", arg)
|
return fmt.Errorf("invalid resource name: %q", arg)
|
||||||
|
|
@ -301,6 +313,9 @@ func (c *Command) specialCmd(p *cmd.Interpreter, pushCmd bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) viewMetaFor(p *cmd.Interpreter) (*client.GVR, *MetaViewer, *cmd.Interpreter, error) {
|
func (c *Command) viewMetaFor(p *cmd.Interpreter) (*client.GVR, *MetaViewer, *cmd.Interpreter, error) {
|
||||||
|
if c.alias == nil {
|
||||||
|
return client.NoGVR, nil, nil, fmt.Errorf("no connection available")
|
||||||
|
}
|
||||||
gvr, ok := c.alias.Resolve(p)
|
gvr, ok := c.alias.Resolve(p)
|
||||||
if !ok {
|
if !ok {
|
||||||
return client.NoGVR, nil, nil, fmt.Errorf("`%s` command not found", p.Cmd())
|
return client.NoGVR, nil, nil, fmt.Errorf("`%s` command not found", p.Cmd())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue