parent
f5f3278e17
commit
9e337a6be0
2
Makefile
2
Makefile
|
|
@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
|
||||||
else
|
else
|
||||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
endif
|
endif
|
||||||
VERSION ?= v0.30.4
|
VERSION ?= v0.30.5
|
||||||
IMG_NAME := derailed/k9s
|
IMG_NAME := derailed/k9s
|
||||||
IMAGE := ${IMG_NAME}:${VERSION}
|
IMAGE := ${IMG_NAME}:${VERSION}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s-xmas.png" align="center" width="800" height="auto"/>
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
25
cmd/root.go
25
cmd/root.go
|
|
@ -91,7 +91,11 @@ func run(cmd *cobra.Command, args []string) error {
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: file})
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: file})
|
||||||
zerolog.SetGlobalLevel(parseLevel(*k9sFlags.LogLevel))
|
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 {
|
if err := app.Init(version, *k9sFlags.RefreshRate); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +109,7 @@ func run(cmd *cobra.Command, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfiguration() *config.Config {
|
func loadConfiguration() (*config.Config, error) {
|
||||||
log.Info().Msg("🐶 K9s starting up...")
|
log.Info().Msg("🐶 K9s starting up...")
|
||||||
|
|
||||||
k8sCfg := client.NewConfig(k8sFlags)
|
k8sCfg := client.NewConfig(k8sFlags)
|
||||||
|
|
@ -116,26 +120,29 @@ func loadConfiguration() *config.Config {
|
||||||
k9sCfg.K9s.Override(k9sFlags)
|
k9sCfg.K9s.Override(k9sFlags)
|
||||||
if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil {
|
if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil {
|
||||||
log.Error().Err(err).Msgf("refine failed")
|
log.Error().Err(err).Msgf("refine failed")
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
conn, err := client.InitConnection(k8sCfg)
|
conn, err := client.InitConnection(k8sCfg)
|
||||||
k9sCfg.SetConnection(conn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("failed to connect to context %q", k9sCfg.K9s.ActiveContextName())
|
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?
|
// Try to access server version if that fail. Connectivity issue?
|
||||||
if !k9sCfg.GetConnection().CheckConnectivity() {
|
if !conn.CheckConnectivity() {
|
||||||
log.Panic().Msgf("Cannot connect to context %s", k9sCfg.K9s.ActiveContextName())
|
return nil, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName())
|
||||||
}
|
}
|
||||||
if !k9sCfg.GetConnection().ConnectionOK() {
|
if !conn.ConnectionOK() {
|
||||||
panic("No connectivity")
|
return nil, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName())
|
||||||
}
|
}
|
||||||
|
k9sCfg.SetConnection(conn)
|
||||||
|
|
||||||
log.Info().Msg("✅ Kubernetes connectivity")
|
log.Info().Msg("✅ Kubernetes connectivity")
|
||||||
if err := k9sCfg.Save(); err != nil {
|
if err := k9sCfg.Save(); err != nil {
|
||||||
log.Error().Err(err).Msg("Config save")
|
log.Error().Err(err).Msg("Config save")
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return k9sCfg
|
return k9sCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLevel(level string) zerolog.Level {
|
func parseLevel(level string) zerolog.Level {
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ type Config struct {
|
||||||
|
|
||||||
// K9sHome returns k9s configs home directory.
|
// K9sHome returns k9s configs home directory.
|
||||||
func K9sHome() string {
|
func K9sHome() string {
|
||||||
if env := os.Getenv(K9sConfigDir); env != "" {
|
if isEnvSet(K9sEnvConfigDir) {
|
||||||
return env
|
return os.Getenv(K9sEnvConfigDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
xdgK9sHome, err := xdg.ConfigFile(AppName)
|
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())
|
log.Debug().Msgf("Active Context %q", c.K9s.ActiveContextName())
|
||||||
|
|
||||||
var ns = client.DefaultNamespace
|
var ns string
|
||||||
switch {
|
switch {
|
||||||
case k9sFlags != nil && IsBoolSet(k9sFlags.AllNamespaces):
|
case k9sFlags != nil && IsBoolSet(k9sFlags.AllNamespaces):
|
||||||
ns = client.NamespaceAll
|
ns = client.NamespaceAll
|
||||||
|
|
@ -97,10 +97,12 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c
|
||||||
}
|
}
|
||||||
ns = nss
|
ns = nss
|
||||||
}
|
}
|
||||||
|
if ns == "" {
|
||||||
|
ns = client.DefaultNamespace
|
||||||
|
}
|
||||||
if err := c.SetActiveNamespace(ns); err != nil {
|
if err := c.SetActiveNamespace(ns); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
flags.Namespace = &ns
|
|
||||||
|
|
||||||
return data.EnsureDirPath(c.K9s.GetScreenDumpDir(), data.DefaultDirMod)
|
return data.EnsureDirPath(c.K9s.GetScreenDumpDir(), data.DefaultDirMod)
|
||||||
}
|
}
|
||||||
|
|
@ -139,10 +141,10 @@ func (c *Config) ActiveNamespace() string {
|
||||||
// ValidateFavorites ensure favorite ns are legit.
|
// ValidateFavorites ensure favorite ns are legit.
|
||||||
func (c *Config) ValidateFavorites() {
|
func (c *Config) ValidateFavorites() {
|
||||||
ct, err := c.K9s.ActiveContext()
|
ct, err := c.K9s.ActiveContext()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
ct.Validate(c.conn, c.settings)
|
return
|
||||||
ct.Namespace.Validate(c.conn, c.settings)
|
|
||||||
}
|
}
|
||||||
|
ct.Validate(c.conn, c.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FavNamespaces returns fav namespaces in the current context.
|
// FavNamespaces returns fav namespaces in the current context.
|
||||||
|
|
@ -197,8 +199,10 @@ func (c *Config) GetConnection() client.Connection {
|
||||||
|
|
||||||
// SetConnection set an api server connection.
|
// SetConnection set an api server connection.
|
||||||
func (c *Config) SetConnection(conn client.Connection) {
|
func (c *Config) SetConnection(conn client.Connection) {
|
||||||
c.conn, c.K9s.conn = conn, conn
|
c.conn = conn
|
||||||
c.Validate()
|
if conn != nil {
|
||||||
|
c.K9s.resetConnection(conn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) ActiveContextName() string {
|
func (c *Config) ActiveContextName() string {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) {
|
||||||
if c.PortForwardAddress == "" {
|
if c.PortForwardAddress == "" {
|
||||||
c.PortForwardAddress = DefaultPFAddress
|
c.PortForwardAddress = DefaultPFAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
if cl, err := ks.CurrentClusterName(); err != nil {
|
if cl, err := ks.CurrentClusterName(); err != nil {
|
||||||
c.ClusterName = cl
|
c.ClusterName = cl
|
||||||
}
|
}
|
||||||
|
|
@ -55,9 +54,6 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) {
|
||||||
if c.Namespace == nil {
|
if c.Namespace == nil {
|
||||||
c.Namespace = NewNamespace()
|
c.Namespace = NewNamespace()
|
||||||
}
|
}
|
||||||
if c.Namespace.Active == client.BlankNamespace {
|
|
||||||
c.Namespace.Active = client.DefaultNamespace
|
|
||||||
}
|
|
||||||
c.Namespace.Validate(conn, ks)
|
c.Namespace.Validate(conn, ks)
|
||||||
|
|
||||||
if c.View == nil {
|
if c.View == nil {
|
||||||
|
|
|
||||||
|
|
@ -8,31 +8,28 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"k8s.io/client-go/tools/clientcmd/api"
|
"k8s.io/client-go/tools/clientcmd/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Dir tracks context configurations.
|
||||||
type Dir struct {
|
type Dir struct {
|
||||||
root string
|
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{
|
return &Dir{
|
||||||
root: root,
|
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 {
|
if ct == nil {
|
||||||
return nil, errors.New("api.Context must not be nil")
|
return nil, errors.New("api.Context must not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
path = filepath.Join(d.root, SanitizeContextSubpath(ct.Cluster, n), MainConfigFile)
|
path = filepath.Join(d.root, SanitizeContextSubpath(ct.Cluster, n), MainConfigFile)
|
||||||
cfg *Config
|
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) {
|
func (d *Dir) genConfig(path string, ct *api.Context) (*Config, error) {
|
||||||
cfg := NewConfig(ct)
|
cfg := NewConfig(ct)
|
||||||
cfg.Validate(d.conn, d.ks)
|
|
||||||
if err := cfg.Save(path); err != nil {
|
if err := cfg.Save(path); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +64,6 @@ func (d *Dir) loadConfig(path string) (*Config, error) {
|
||||||
if err := yaml.Unmarshal(bb, &cfg); err != nil {
|
if err := yaml.Unmarshal(bb, &cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cfg.Validate(d.conn, d.ks)
|
|
||||||
|
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ func TestDirLoad(t *testing.T) {
|
||||||
assert.NoError(t, mock.EnsureDir(u.dir))
|
assert.NoError(t, mock.EnsureDir(u.dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
d := data.NewDir(u.dir, mock.NewMockConnection(), ks)
|
d := data.NewDir(u.dir)
|
||||||
ct, err := ks.CurrentContext()
|
ct, err := ks.CurrentContext()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -29,28 +29,30 @@ func NewNamespace() *Namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewActiveNamespace(n string) *Namespace {
|
func NewActiveNamespace(n string) *Namespace {
|
||||||
|
if n == client.BlankNamespace {
|
||||||
|
n = client.DefaultNamespace
|
||||||
|
}
|
||||||
return &Namespace{
|
return &Namespace{
|
||||||
Active: n,
|
Active: n,
|
||||||
Favorites: []string{client.DefaultNamespace},
|
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) {
|
func (n *Namespace) Validate(c client.Connection, ks KubeSettings) {
|
||||||
if c == nil {
|
if n.Active == client.BlankNamespace || c == nil {
|
||||||
n = NewActiveNamespace(client.DefaultNamespace)
|
n.Active = client.DefaultNamespace
|
||||||
}
|
}
|
||||||
if c == nil {
|
if c == nil {
|
||||||
log.Debug().Msgf("No connection found. Skipping ns validation")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !n.isAllNamespaces() && !c.IsValidNamespace(n.Active) {
|
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 {
|
for _, ns := range n.Favorites {
|
||||||
if ns != client.NamespaceAll && !c.IsValidNamespace(ns) {
|
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)
|
n.rmFavNS(ns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ package config
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/config/data"
|
"github.com/derailed/k9s/internal/config/data"
|
||||||
|
|
@ -16,11 +15,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// K9sConfigDir represents k9s configuration dir env var.
|
// K9sEnvConfigDir represents k9s configuration dir env var.
|
||||||
K9sConfigDir = "K9S_CONFIG_DIR"
|
K9sEnvConfigDir = "K9S_CONFIG_DIR"
|
||||||
|
|
||||||
// K9sLogsDir represents k9s logs dir env var.
|
// K9sEnvLogsDir represents k9s logs dir env var.
|
||||||
K9sLogsDir = "K9S_LOGS_DIR"
|
K9sEnvLogsDir = "K9S_LOGS_DIR"
|
||||||
|
|
||||||
// AppName tracks k9s app name.
|
// AppName tracks k9s app name.
|
||||||
AppName = "k9s"
|
AppName = "k9s"
|
||||||
|
|
@ -84,14 +83,21 @@ var (
|
||||||
// InitLogsLoc initializes K9s logs location.
|
// InitLogsLoc initializes K9s logs location.
|
||||||
func InitLogLoc() error {
|
func InitLogLoc() error {
|
||||||
var appLogDir string
|
var appLogDir string
|
||||||
if envDir := os.Getenv(K9sLogsDir); envDir != "" {
|
switch {
|
||||||
appLogDir = envDir
|
case isEnvSet(K9sEnvLogsDir):
|
||||||
} else {
|
appLogDir = os.Getenv(K9sEnvLogsDir)
|
||||||
tmpDir, err := userTmpDir()
|
case isEnvSet(K9sEnvConfigDir):
|
||||||
|
tmpDir, err := UserTmpDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
appLogDir = tmpDir
|
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 {
|
if err := data.EnsureFullPath(appLogDir, data.DefaultDirMod); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -103,7 +109,7 @@ func InitLogLoc() error {
|
||||||
|
|
||||||
// InitLocs initializes k9s artifacts locations.
|
// InitLocs initializes k9s artifacts locations.
|
||||||
func InitLocs() error {
|
func InitLocs() error {
|
||||||
if hasK9sConfigEnv() {
|
if isEnvSet(K9sEnvConfigDir) {
|
||||||
return initK9sEnvLocs()
|
return initK9sEnvLocs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +117,7 @@ func InitLocs() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func initK9sEnvLocs() error {
|
func initK9sEnvLocs() error {
|
||||||
AppConfigDir = os.Getenv(K9sConfigDir)
|
AppConfigDir = os.Getenv(K9sEnvConfigDir)
|
||||||
if err := data.EnsureFullPath(AppConfigDir, data.DefaultDirMod); err != nil {
|
if err := data.EnsureFullPath(AppConfigDir, data.DefaultDirMod); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -269,20 +275,3 @@ func EnsureHotkeysCfgFile() (string, error) {
|
||||||
func SkinFileFromName(n string) string {
|
func SkinFileFromName(n string) string {
|
||||||
return filepath.Join(AppSkinsDir, n+".yaml")
|
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,63 @@ package config_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/adrg/xdg"
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/config/data"
|
"github.com/derailed/k9s/internal/config/data"
|
||||||
"github.com/stretchr/testify/assert"
|
"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) {
|
func TestEnsureBenchmarkCfg(t *testing.T) {
|
||||||
os.Setenv(config.K9sConfigDir, "/tmp/test-config")
|
os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config")
|
||||||
assert.NoError(t, config.InitLocs())
|
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, 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))
|
assert.NoError(t, os.WriteFile("/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml", []byte{}, data.DefaultFileMod))
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,6 @@ const (
|
||||||
DefaultCommand = ""
|
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.
|
// Flags represents K9s configuration flags.
|
||||||
type Flags struct {
|
type Flags struct {
|
||||||
RefreshRate *int
|
RefreshRate *int
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,32 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/config/data"
|
"github.com/derailed/k9s/internal/config/data"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
v1 "k8s.io/api/core/v1"
|
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.
|
// InNSList check if ns is in an ns collection.
|
||||||
func InNSList(nn []interface{}, ns string) bool {
|
func InNSList(nn []interface{}, ns string) bool {
|
||||||
ss := make([]string, len(nn))
|
ss := make([]string, len(nn))
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,16 @@ func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s {
|
||||||
Thresholds: NewThreshold(),
|
Thresholds: NewThreshold(),
|
||||||
ShellPod: NewShellPod(),
|
ShellPod: NewShellPod(),
|
||||||
ImageScans: NewImageScans(),
|
ImageScans: NewImageScans(),
|
||||||
dir: data.NewDir(AppContextsDir, conn, ks),
|
dir: data.NewDir(AppContextsDir),
|
||||||
conn: conn,
|
conn: conn,
|
||||||
ks: ks,
|
ks: ks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *K9s) resetConnection(conn client.Connection) {
|
||||||
|
k.conn = conn
|
||||||
|
}
|
||||||
|
|
||||||
// Save saves the k9s config to dis.
|
// Save saves the k9s config to dis.
|
||||||
func (k *K9s) Save() error {
|
func (k *K9s) Save() error {
|
||||||
if k.activeConfig != nil {
|
if k.activeConfig != nil {
|
||||||
|
|
@ -177,19 +181,20 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cfg, err := k.dir.Load(n, ct)
|
k.activeConfig, err = k.dir.Load(n, ct)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
k.activeConfig = cfg
|
|
||||||
// If the context specifies a default namespace, use it!
|
// If the context specifies a default namespace, use it!
|
||||||
if k.conn != nil {
|
if k.conn != nil {
|
||||||
if ns := k.conn.ActiveNamespace(); ns != client.BlankNamespace {
|
if ns := k.conn.ActiveNamespace(); ns != client.BlankNamespace {
|
||||||
k.activeConfig.Context.Namespace.Active = ns
|
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.
|
// 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 = NewThreshold()
|
||||||
}
|
}
|
||||||
k.Thresholds.Validate(c, ks)
|
k.Thresholds.Validate(c, ks)
|
||||||
|
|
||||||
|
if k.activeConfig != nil {
|
||||||
|
k.activeConfig.Validate(c, ks)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
log.Debug().Msgf("SkinWatcher watching %q", config.AppSkinsDir)
|
||||||
return w.Add(config.AppSkinsDir)
|
return w.Add(config.AppSkinsDir)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBenchConfig(t *testing.T) {
|
func TestBenchConfig(t *testing.T) {
|
||||||
os.Setenv(config.K9sConfigDir, "/tmp/test-config")
|
os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config")
|
||||||
assert.NoError(t, config.InitLocs())
|
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")
|
bc, error := config.EnsureBenchmarksCfgFile("cl-1", "ct-1")
|
||||||
assert.NoError(t, error)
|
assert.NoError(t, error)
|
||||||
|
|
@ -29,9 +29,9 @@ func TestBenchConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSkinnedContext(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())
|
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")
|
sf := filepath.Join("..", "config", "testdata", "black_and_wtf.yaml")
|
||||||
raw, err := os.ReadFile(sf)
|
raw, err := os.ReadFile(sf)
|
||||||
|
|
|
||||||
|
|
@ -407,6 +407,10 @@ func (a *App) refreshCluster(context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) switchNS(ns string) error {
|
func (a *App) switchNS(ns string) error {
|
||||||
|
if a.Config.ActiveNamespace() == ns {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if ns == client.ClusterScope {
|
if ns == client.ClusterScope {
|
||||||
ns = client.BlankNamespace
|
ns = client.BlankNamespace
|
||||||
}
|
}
|
||||||
|
|
@ -433,7 +437,7 @@ func (a *App) isValidNS(ns string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.Conn().IsValidNamespace(ns) {
|
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
|
return true, nil
|
||||||
|
|
@ -445,16 +449,9 @@ func (a *App) switchContext(ci *cmd.Interpreter) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msgf("--> Switching Context %q--%q", name, a.Config.ActiveView())
|
|
||||||
a.Halt()
|
a.Halt()
|
||||||
defer a.Resume()
|
defer a.Resume()
|
||||||
{
|
{
|
||||||
p := cmd.NewInterpreter(a.Config.ActiveView())
|
|
||||||
if p.IsContextCmd() {
|
|
||||||
a.Config.SetActiveView("pod")
|
|
||||||
}
|
|
||||||
p.ResetContextArg()
|
|
||||||
|
|
||||||
a.Config.Reset()
|
a.Config.Reset()
|
||||||
ct, err := a.Config.K9s.ActivateContext(name)
|
ct, err := a.Config.K9s.ActivateContext(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -466,14 +463,26 @@ func (a *App) switchContext(ci *cmd.Interpreter) error {
|
||||||
if cns, ok := ci.NSArg(); ok {
|
if cns, ok := ci.NSArg(); ok {
|
||||||
ct.Namespace.Active = cns
|
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 {
|
if err := a.Config.Save(); err != nil {
|
||||||
log.Error().Err(err).Msg("config save failed!")
|
log.Error().Err(err).Msg("config save failed!")
|
||||||
}
|
}
|
||||||
|
|
||||||
ns := a.Config.ActiveNamespace()
|
|
||||||
a.initFactory(ns)
|
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.ReloadStyles(name)
|
||||||
a.gotoResource(a.Config.ActiveView(), "", true)
|
a.gotoResource(a.Config.ActiveView(), "", true)
|
||||||
a.clusterModel.Reset(a.factory)
|
a.clusterModel.Reset(a.factory)
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,6 @@ 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()
|
|
||||||
ns := b.app.Config.ActiveNamespace()
|
ns := b.app.Config.ActiveNamespace()
|
||||||
if n := b.GetModel().GetNamespace(); !client.IsClusterScoped(n) {
|
if n := b.GetModel().GetNamespace(); !client.IsClusterScoped(n) {
|
||||||
ns = n
|
ns = n
|
||||||
|
|
|
||||||
|
|
@ -131,10 +131,6 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool) error {
|
||||||
if c.specialCmd(p) {
|
if c.specialCmd(p) {
|
||||||
return nil
|
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)
|
gvr, v, err := c.viewMetaFor(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/color"
|
"github.com/derailed/k9s/internal/color"
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
|
"github.com/derailed/k9s/internal/config/data"
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/derailed/k9s/internal/model"
|
"github.com/derailed/k9s/internal/model"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
|
|
@ -420,19 +421,17 @@ func ensureDir(dir string) error {
|
||||||
return os.MkdirAll(dir, 0744)
|
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 {
|
if err := ensureDir(dir); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UnixNano()
|
f := fmt.Sprintf("%s-%d.log", fqn, time.Now().UnixNano())
|
||||||
fName := fmt.Sprintf("%s-%d.log", strings.Replace(fqn, "/", "-", 1), now)
|
path := filepath.Join(dir, data.SanitizeFileName(f))
|
||||||
|
|
||||||
path := filepath.Join(dir, fName)
|
|
||||||
mod := os.O_CREATE | os.O_WRONLY
|
mod := os.O_CREATE | os.O_WRONLY
|
||||||
file, err := os.OpenFile(path, mod, 0600)
|
file, err := os.OpenFile(path, mod, 0600)
|
||||||
if err != nil {
|
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
|
return "", nil
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|
@ -440,7 +439,7 @@ func saveData(dir, fqn, data string) (string, error) {
|
||||||
log.Error().Err(err).Msg("Closing Log file")
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
name: k9s
|
name: k9s
|
||||||
base: core20
|
base: core20
|
||||||
version: 'v0.30.4'
|
version: 'v0.30.5'
|
||||||
summary: K9s is a CLI to view and manage your Kubernetes clusters.
|
summary: K9s is a CLI to view and manage your Kubernetes clusters.
|
||||||
description: |
|
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.
|
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.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue