diff --git a/Makefile b/Makefile index a84db0a79f..7dc2db8daf 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.30.4 +VERSION ?= v0.30.5 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.30.5.md b/change_logs/release_v0.30.5.md new file mode 100644 index 0000000000..b59cf29af1 --- /dev/null +++ b/change_logs/release_v0.30.5.md @@ -0,0 +1,52 @@ + + +# Release v0.30.5 + +## 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 are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or 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) + +## 🎄 Maintenance Release! 🎄 + +Thank you all for pitching in and helping flesh out issues!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2394](https://github.com/derailed/k9s/issues/2394) Allow setting custom log dir +* [#2393](https://github.com/derailed/k9s/issues/2393) When switching contexts k9s does not switching to cluster's pod/namespaces/other k8s kinds view +* [#2387](https://github.com/derailed/k9s/issues/2387) Invalid namespace xxx - with feelings! + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2396](https://github.com/derailed/k9s/pull/2396) feat: allow to customize logs dir through environment variable +* [#2395](https://github.com/derailed/k9s/pull/2395) fix: create user tmp directory before the app one + +--- + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/cmd/root.go b/cmd/root.go index 703ddf289f..079ec37e45 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -91,7 +91,11 @@ func run(cmd *cobra.Command, args []string) error { log.Logger = log.Output(zerolog.ConsoleWriter{Out: file}) zerolog.SetGlobalLevel(parseLevel(*k9sFlags.LogLevel)) - app := view.NewApp(loadConfiguration()) + cfg, err := loadConfiguration() + if err != nil { + return err + } + app := view.NewApp(cfg) if err := app.Init(version, *k9sFlags.RefreshRate); err != nil { return err } @@ -105,7 +109,7 @@ func run(cmd *cobra.Command, args []string) error { return nil } -func loadConfiguration() *config.Config { +func loadConfiguration() (*config.Config, error) { log.Info().Msg("🐶 K9s starting up...") k8sCfg := client.NewConfig(k8sFlags) @@ -116,26 +120,29 @@ func loadConfiguration() *config.Config { k9sCfg.K9s.Override(k9sFlags) if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil { log.Error().Err(err).Msgf("refine failed") + return nil, err } conn, err := client.InitConnection(k8sCfg) - k9sCfg.SetConnection(conn) if err != nil { log.Error().Err(err).Msgf("failed to connect to context %q", k9sCfg.K9s.ActiveContextName()) - return k9sCfg + return nil, err } // Try to access server version if that fail. Connectivity issue? - if !k9sCfg.GetConnection().CheckConnectivity() { - log.Panic().Msgf("Cannot connect to context %s", k9sCfg.K9s.ActiveContextName()) + if !conn.CheckConnectivity() { + return nil, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()) } - if !k9sCfg.GetConnection().ConnectionOK() { - panic("No connectivity") + if !conn.ConnectionOK() { + return nil, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName()) } + k9sCfg.SetConnection(conn) + log.Info().Msg("✅ Kubernetes connectivity") if err := k9sCfg.Save(); err != nil { log.Error().Err(err).Msg("Config save") + return nil, err } - return k9sCfg + return k9sCfg, nil } func parseLevel(level string) zerolog.Level { diff --git a/internal/config/config.go b/internal/config/config.go index d4e8e81b7a..c4b781b27d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -26,8 +26,8 @@ type Config struct { // K9sHome returns k9s configs home directory. func K9sHome() string { - if env := os.Getenv(K9sConfigDir); env != "" { - return env + if isEnvSet(K9sEnvConfigDir) { + return os.Getenv(K9sEnvConfigDir) } xdgK9sHome, err := xdg.ConfigFile(AppName) @@ -84,7 +84,7 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c } log.Debug().Msgf("Active Context %q", c.K9s.ActiveContextName()) - var ns = client.DefaultNamespace + var ns string switch { case k9sFlags != nil && IsBoolSet(k9sFlags.AllNamespaces): ns = client.NamespaceAll @@ -97,10 +97,12 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c } ns = nss } + if ns == "" { + ns = client.DefaultNamespace + } if err := c.SetActiveNamespace(ns); err != nil { return err } - flags.Namespace = &ns return data.EnsureDirPath(c.K9s.GetScreenDumpDir(), data.DefaultDirMod) } @@ -139,10 +141,10 @@ func (c *Config) ActiveNamespace() string { // ValidateFavorites ensure favorite ns are legit. func (c *Config) ValidateFavorites() { ct, err := c.K9s.ActiveContext() - if err == nil { - ct.Validate(c.conn, c.settings) - ct.Namespace.Validate(c.conn, c.settings) + if err != nil { + return } + ct.Validate(c.conn, c.settings) } // FavNamespaces returns fav namespaces in the current context. @@ -197,8 +199,10 @@ func (c *Config) GetConnection() client.Connection { // SetConnection set an api server connection. func (c *Config) SetConnection(conn client.Connection) { - c.conn, c.K9s.conn = conn, conn - c.Validate() + c.conn = conn + if conn != nil { + c.K9s.resetConnection(conn) + } } func (c *Config) ActiveContextName() string { diff --git a/internal/config/data/context.go b/internal/config/data/context.go index e08de8ffa2..081b133822 100644 --- a/internal/config/data/context.go +++ b/internal/config/data/context.go @@ -47,7 +47,6 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) { if c.PortForwardAddress == "" { c.PortForwardAddress = DefaultPFAddress } - if cl, err := ks.CurrentClusterName(); err != nil { c.ClusterName = cl } @@ -55,9 +54,6 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) { if c.Namespace == nil { c.Namespace = NewNamespace() } - if c.Namespace.Active == client.BlankNamespace { - c.Namespace.Active = client.DefaultNamespace - } c.Namespace.Validate(conn, ks) if c.View == nil { diff --git a/internal/config/data/dir.go b/internal/config/data/dir.go index b8e5d88d07..d20e545652 100644 --- a/internal/config/data/dir.go +++ b/internal/config/data/dir.go @@ -8,31 +8,28 @@ import ( "os" "path/filepath" - "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" "k8s.io/client-go/tools/clientcmd/api" ) +// Dir tracks context configurations. type Dir struct { root string - conn client.Connection - ks KubeSettings } -func NewDir(root string, conn client.Connection, ks KubeSettings) *Dir { +// NewDir returns a new instance. +func NewDir(root string) *Dir { return &Dir{ root: root, - ks: ks, - conn: conn, } } -func (d Dir) Load(n string, ct *api.Context) (*Config, error) { +// Load loads context configuration. +func (d *Dir) Load(n string, ct *api.Context) (*Config, error) { if ct == nil { return nil, errors.New("api.Context must not be nil") } - var ( path = filepath.Join(d.root, SanitizeContextSubpath(ct.Cluster, n), MainConfigFile) cfg *Config @@ -51,7 +48,6 @@ func (d Dir) Load(n string, ct *api.Context) (*Config, error) { func (d *Dir) genConfig(path string, ct *api.Context) (*Config, error) { cfg := NewConfig(ct) - cfg.Validate(d.conn, d.ks) if err := cfg.Save(path); err != nil { return nil, err } @@ -68,7 +64,6 @@ func (d *Dir) loadConfig(path string) (*Config, error) { if err := yaml.Unmarshal(bb, &cfg); err != nil { return nil, err } - cfg.Validate(d.conn, d.ks) return &cfg, nil } diff --git a/internal/config/data/dir_test.go b/internal/config/data/dir_test.go index 56d0bc67ce..b780a585c6 100644 --- a/internal/config/data/dir_test.go +++ b/internal/config/data/dir_test.go @@ -71,7 +71,7 @@ func TestDirLoad(t *testing.T) { assert.NoError(t, mock.EnsureDir(u.dir)) } - d := data.NewDir(u.dir, mock.NewMockConnection(), ks) + d := data.NewDir(u.dir) ct, err := ks.CurrentContext() assert.NoError(t, err) if err != nil { diff --git a/internal/config/data/ns.go b/internal/config/data/ns.go index 252b4f5750..e9e9379cb5 100644 --- a/internal/config/data/ns.go +++ b/internal/config/data/ns.go @@ -29,28 +29,30 @@ func NewNamespace() *Namespace { } func NewActiveNamespace(n string) *Namespace { + if n == client.BlankNamespace { + n = client.DefaultNamespace + } return &Namespace{ Active: n, Favorites: []string{client.DefaultNamespace}, } } -// Validate a namespace is setup correctly. +// Validate validates a namespace is setup correctly. func (n *Namespace) Validate(c client.Connection, ks KubeSettings) { - if c == nil { - n = NewActiveNamespace(client.DefaultNamespace) + if n.Active == client.BlankNamespace || c == nil { + n.Active = client.DefaultNamespace } if c == nil { - log.Debug().Msgf("No connection found. Skipping ns validation") return } if !n.isAllNamespaces() && !c.IsValidNamespace(n.Active) { - log.Error().Msgf("[Config] Validation error active namespace %q does not exists", n.Active) + log.Error().Msgf("[Config] Validation failed active namespace %q does not exists. Resetting to default ns", n.Active) + n.Active = client.DefaultNamespace } - for _, ns := range n.Favorites { if ns != client.NamespaceAll && !c.IsValidNamespace(ns) { - log.Debug().Msgf("[Config] Invalid favorite found '%s' - %t", ns, n.isAllNamespaces()) + log.Debug().Msgf("[Namespace] Invalid favorite found '%s' - %t", ns, n.isAllNamespaces()) n.rmFavNS(ns) } } diff --git a/internal/config/files.go b/internal/config/files.go index 4913210f29..73ee51b6e1 100644 --- a/internal/config/files.go +++ b/internal/config/files.go @@ -6,7 +6,6 @@ package config import ( _ "embed" "os" - "os/user" "path/filepath" "github.com/derailed/k9s/internal/config/data" @@ -16,11 +15,11 @@ import ( ) const ( - // K9sConfigDir represents k9s configuration dir env var. - K9sConfigDir = "K9S_CONFIG_DIR" + // K9sEnvConfigDir represents k9s configuration dir env var. + K9sEnvConfigDir = "K9S_CONFIG_DIR" - // K9sLogsDir represents k9s logs dir env var. - K9sLogsDir = "K9S_LOGS_DIR" + // K9sEnvLogsDir represents k9s logs dir env var. + K9sEnvLogsDir = "K9S_LOGS_DIR" // AppName tracks k9s app name. AppName = "k9s" @@ -84,14 +83,21 @@ var ( // InitLogsLoc initializes K9s logs location. func InitLogLoc() error { var appLogDir string - if envDir := os.Getenv(K9sLogsDir); envDir != "" { - appLogDir = envDir - } else { - tmpDir, err := userTmpDir() + switch { + case isEnvSet(K9sEnvLogsDir): + appLogDir = os.Getenv(K9sEnvLogsDir) + case isEnvSet(K9sEnvConfigDir): + tmpDir, err := UserTmpDir() if err != nil { return err } appLogDir = tmpDir + default: + var err error + appLogDir, err = xdg.StateFile(AppName) + if err != nil { + return err + } } if err := data.EnsureFullPath(appLogDir, data.DefaultDirMod); err != nil { return err @@ -103,7 +109,7 @@ func InitLogLoc() error { // InitLocs initializes k9s artifacts locations. func InitLocs() error { - if hasK9sConfigEnv() { + if isEnvSet(K9sEnvConfigDir) { return initK9sEnvLocs() } @@ -111,7 +117,7 @@ func InitLocs() error { } func initK9sEnvLocs() error { - AppConfigDir = os.Getenv(K9sConfigDir) + AppConfigDir = os.Getenv(K9sEnvConfigDir) if err := data.EnsureFullPath(AppConfigDir, data.DefaultDirMod); err != nil { return err } @@ -269,20 +275,3 @@ func EnsureHotkeysCfgFile() (string, error) { func SkinFileFromName(n string) string { return filepath.Join(AppSkinsDir, n+".yaml") } - -// Helpers... - -func hasK9sConfigEnv() bool { - return os.Getenv(K9sConfigDir) != "" -} - -func userTmpDir() (string, error) { - u, err := user.Current() - if err != nil { - return "", err - } - - dir := filepath.Join(os.TempDir(), u.Username, AppName) - - return dir, nil -} diff --git a/internal/config/files_test.go b/internal/config/files_test.go index ca204e09ee..fb946ff2ff 100644 --- a/internal/config/files_test.go +++ b/internal/config/files_test.go @@ -5,17 +5,63 @@ package config_test import ( "os" + "path/filepath" "testing" + "github.com/adrg/xdg" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config/data" "github.com/stretchr/testify/assert" ) +func TestInitLogLoc(t *testing.T) { + tmp, err := config.UserTmpDir() + assert.NoError(t, err) + + uu := map[string]struct { + dir string + e string + }{ + "log-env": { + dir: "/tmp/test/k9s/logs", + e: "/tmp/test/k9s/logs/k9s.log", + }, + "xdg-env": { + dir: "/tmp/test/xdg-state", + e: "/tmp/test/xdg-state/k9s/k9s.log", + }, + "cfg-env": { + dir: "/tmp/test/k9s-test", + e: filepath.Join(tmp, "k9s.log"), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + os.Unsetenv(config.K9sEnvLogsDir) + os.Unsetenv("XDG_STATE_HOME") + os.Unsetenv(config.K9sEnvConfigDir) + switch k { + case "log-env": + os.Setenv(config.K9sEnvLogsDir, u.dir) + case "xdg-env": + os.Setenv("XDG_STATE_HOME", u.dir) + xdg.Reload() + case "cfg-env": + os.Setenv(config.K9sEnvConfigDir, u.dir) + } + err := config.InitLogLoc() + assert.NoError(t, err) + assert.Equal(t, u.e, config.AppLogFile) + assert.NoError(t, os.RemoveAll(config.AppLogFile)) + }) + } +} func TestEnsureBenchmarkCfg(t *testing.T) { - os.Setenv(config.K9sConfigDir, "/tmp/test-config") + os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") assert.NoError(t, config.InitLocs()) - defer assert.NoError(t, os.RemoveAll(config.K9sConfigDir)) + defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir)) assert.NoError(t, data.EnsureFullPath("/tmp/test-config/clusters/cl-1/ct-2", data.DefaultDirMod)) assert.NoError(t, os.WriteFile("/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml", []byte{}, data.DefaultFileMod)) diff --git a/internal/config/flags.go b/internal/config/flags.go index 8e7a78db45..a1fc7699c3 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -14,9 +14,6 @@ const ( DefaultCommand = "" ) -// DefaultLogFile represents the default K9s log file. -// var DefaultLogFile = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", MustK9sUser())) - // Flags represents K9s configuration flags. type Flags struct { RefreshRate *int diff --git a/internal/config/helpers.go b/internal/config/helpers.go index 0e8d0c2d1d..752644d7d3 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -4,13 +4,32 @@ package config import ( + "os" "os/user" + "path/filepath" "github.com/derailed/k9s/internal/config/data" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" ) +// isEnvSet checks if env var is set. +func isEnvSet(env string) bool { + return os.Getenv(env) != "" +} + +// UserTmpDir returns the temp dir with the current user name. +func UserTmpDir() (string, error) { + u, err := user.Current() + if err != nil { + return "", err + } + + dir := filepath.Join(os.TempDir(), u.Username, AppName) + + return dir, nil +} + // InNSList check if ns is in an ns collection. func InNSList(nn []interface{}, ns string) bool { ss := make([]string, len(nn)) diff --git a/internal/config/k9s.go b/internal/config/k9s.go index fd8bdae67b..56c8922368 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -50,12 +50,16 @@ func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s { Thresholds: NewThreshold(), ShellPod: NewShellPod(), ImageScans: NewImageScans(), - dir: data.NewDir(AppContextsDir, conn, ks), + dir: data.NewDir(AppContextsDir), conn: conn, ks: ks, } } +func (k *K9s) resetConnection(conn client.Connection) { + k.conn = conn +} + // Save saves the k9s config to dis. func (k *K9s) Save() error { if k.activeConfig != nil { @@ -177,19 +181,20 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) { if err != nil { return nil, err } - cfg, err := k.dir.Load(n, ct) + k.activeConfig, err = k.dir.Load(n, ct) if err != nil { return nil, err } - k.activeConfig = cfg // If the context specifies a default namespace, use it! if k.conn != nil { if ns := k.conn.ActiveNamespace(); ns != client.BlankNamespace { k.activeConfig.Context.Namespace.Active = ns + } else { + k.activeConfig.Context.Namespace.Active = client.DefaultNamespace } } - return cfg.Context, nil + return k.activeConfig.Context, nil } // OverrideRefreshRate set the refresh rate manually. @@ -319,4 +324,8 @@ func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) { k.Thresholds = NewThreshold() } k.Thresholds.Validate(c, ks) + + if k.activeConfig != nil { + k.activeConfig.Validate(c, ks) + } } diff --git a/internal/ui/config.go b/internal/ui/config.go index 6ca8a4951a..11539ea3d8 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -116,10 +116,6 @@ func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error } }() - log.Debug().Msgf("SkinWatcher watching %q", config.K9sHome()) - if err := w.Add(config.K9sHome()); err != nil { - return err - } log.Debug().Msgf("SkinWatcher watching %q", config.AppSkinsDir) return w.Add(config.AppSkinsDir) } diff --git a/internal/ui/config_test.go b/internal/ui/config_test.go index 9234131738..3a91554ccf 100644 --- a/internal/ui/config_test.go +++ b/internal/ui/config_test.go @@ -19,9 +19,9 @@ import ( ) func TestBenchConfig(t *testing.T) { - os.Setenv(config.K9sConfigDir, "/tmp/test-config") + os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") assert.NoError(t, config.InitLocs()) - defer assert.NoError(t, os.RemoveAll(config.K9sConfigDir)) + defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir)) bc, error := config.EnsureBenchmarksCfgFile("cl-1", "ct-1") assert.NoError(t, error) @@ -29,9 +29,9 @@ func TestBenchConfig(t *testing.T) { } func TestSkinnedContext(t *testing.T) { - os.Setenv(config.K9sConfigDir, "/tmp/test-config") + os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") assert.NoError(t, config.InitLocs()) - defer assert.NoError(t, os.RemoveAll(config.K9sConfigDir)) + defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir)) sf := filepath.Join("..", "config", "testdata", "black_and_wtf.yaml") raw, err := os.ReadFile(sf) diff --git a/internal/view/app.go b/internal/view/app.go index bbba78a28d..ab9431e760 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -407,6 +407,10 @@ func (a *App) refreshCluster(context.Context) error { } func (a *App) switchNS(ns string) error { + if a.Config.ActiveNamespace() == ns { + return nil + } + if ns == client.ClusterScope { ns = client.BlankNamespace } @@ -433,7 +437,7 @@ func (a *App) isValidNS(ns string) (bool, error) { } if !a.Conn().IsValidNamespace(ns) { - return false, fmt.Errorf("isvalidns - invalid namespace: %q", ns) + return false, fmt.Errorf("invalid namespace: %q", ns) } return true, nil @@ -445,16 +449,9 @@ func (a *App) switchContext(ci *cmd.Interpreter) error { return nil } - log.Debug().Msgf("--> Switching Context %q--%q", name, a.Config.ActiveView()) a.Halt() defer a.Resume() { - p := cmd.NewInterpreter(a.Config.ActiveView()) - if p.IsContextCmd() { - a.Config.SetActiveView("pod") - } - p.ResetContextArg() - a.Config.Reset() ct, err := a.Config.K9s.ActivateContext(name) if err != nil { @@ -466,14 +463,26 @@ func (a *App) switchContext(ci *cmd.Interpreter) error { if cns, ok := ci.NSArg(); ok { ct.Namespace.Active = cns } + + p := cmd.NewInterpreter(a.Config.ActiveView()) + p.ResetContextArg() + if p.IsContextCmd() { + a.Config.SetActiveView("pod") + } + ns := a.Config.ActiveNamespace() + if !a.Conn().IsValidNamespace(ns) { + ns = client.DefaultNamespace + if err := a.Config.SetActiveNamespace(ns); err != nil { + return err + } + } if err := a.Config.Save(); err != nil { log.Error().Err(err).Msg("config save failed!") } - - ns := a.Config.ActiveNamespace() a.initFactory(ns) - a.Flash().Infof("Switching context to %s", name) + log.Debug().Msgf("--> Switching Context %q -- %q -- %q", name, ns, a.Config.ActiveView()) + a.Flash().Infof("Switching context to %q::%q", name, ns) a.ReloadStyles(name) a.gotoResource(a.Config.ActiveView(), "", true) a.clusterModel.Reset(a.factory) diff --git a/internal/view/browser.go b/internal/view/browser.go index 429e3e94bb..21176d69ba 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -133,7 +133,6 @@ func (b *Browser) SetInstance(path string) { // Start initializes browser updates. func (b *Browser) Start() { - b.app.Config.ValidateFavorites() ns := b.app.Config.ActiveNamespace() if n := b.GetModel().GetNamespace(); !client.IsClusterScoped(n) { ns = n diff --git a/internal/view/command.go b/internal/view/command.go index 0fdb7f7a8f..3604c89aa1 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -131,10 +131,6 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool) error { if c.specialCmd(p) { return nil } - // if _, ok := c.alias.Check(p.Cmd()); !ok { - // return fmt.Errorf("command not found %q", p.Cmd()) - // } - gvr, v, err := c.viewMetaFor(p) if err != nil { return err diff --git a/internal/view/log.go b/internal/view/log.go index b6120bc6b2..88b0cd8bfe 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -16,6 +16,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/ui" @@ -420,19 +421,17 @@ func ensureDir(dir string) error { return os.MkdirAll(dir, 0744) } -func saveData(dir, fqn, data string) (string, error) { +func saveData(dir, fqn, logs string) (string, error) { if err := ensureDir(dir); err != nil { return "", err } - now := time.Now().UnixNano() - fName := fmt.Sprintf("%s-%d.log", strings.Replace(fqn, "/", "-", 1), now) - - path := filepath.Join(dir, fName) + f := fmt.Sprintf("%s-%d.log", fqn, time.Now().UnixNano()) + path := filepath.Join(dir, data.SanitizeFileName(f)) mod := os.O_CREATE | os.O_WRONLY file, err := os.OpenFile(path, mod, 0600) if err != nil { - log.Error().Err(err).Msgf("LogFile create %s", path) + log.Error().Err(err).Msgf("Log file save failed: %q", path) return "", nil } defer func() { @@ -440,7 +439,7 @@ func saveData(dir, fqn, data string) (string, error) { log.Error().Err(err).Msg("Closing Log file") } }() - if _, err := file.Write([]byte(data)); err != nil { + if _, err := file.WriteString(logs); err != nil { return "", err } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 342a0a8e1d..c83d786315 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.30.4' +version: 'v0.30.5' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session.