diff --git a/Makefile b/Makefile
index 298dfa8d..a84db0a7 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.3
+VERSION ?= v0.30.4
IMG_NAME := derailed/k9s
IMAGE := ${IMG_NAME}:${VERSION}
diff --git a/change_logs/release_v0.30.4.md b/change_logs/release_v0.30.4.md
new file mode 100644
index 00000000..c7a1ad20
--- /dev/null
+++ b/change_logs/release_v0.30.4.md
@@ -0,0 +1,52 @@
+
+
+# Release v0.30.4
+
+## 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
+
+* [#2391](https://github.com/derailed/k9s/issues/2391) Version 0.30.* has issues with : chars in the cluster names from AWS
+* [#2397](https://github.com/derailed/k9s/issues/2387) Error: invalid namespace xxx
+* [#2389](https://github.com/derailed/k9s/issues/2389) Mixed-case named contexts cannot be switched to from contexts view
+* [#2382](https://github.com/derailed/k9s/issues/2382) Header always shows Cluster from kubeconfig current-context
+
+---
+
+## 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!!
+
+* [#2390](https://github.com/derailed/k9s/pull/2390) case sensitive for specific command args and flags
+
+---
+
+
© 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
diff --git a/internal/client/client.go b/internal/client/client.go
index 577c3a3f..66bcf3fa 100644
--- a/internal/client/client.go
+++ b/internal/client/client.go
@@ -216,15 +216,59 @@ func (a *APIClient) IsValidNamespace(ns string) bool {
if IsAllNamespace(ns) {
return true
}
+
+ ok, err := a.CanI(ClusterScope, "v1/namespaces", []string{ListVerb})
+ if !ok || err != nil {
+ cool, err := a.isValidNamespace(ns)
+ if err != nil {
+ log.Error().Err(err).Msgf("unable to assert valid namespace")
+ }
+ return cool
+ }
nn, err := a.ValidNamespaceNames()
if err != nil {
return false
}
- _, ok := nn[ns]
+ _, ok = nn[ns]
return ok
}
+func (a *APIClient) cachedNamespaceNames() NamespaceNames {
+ cns, ok := a.cache.Get("validNamespaces")
+ if !ok {
+ return make(NamespaceNames)
+ }
+
+ return cns.(NamespaceNames)
+}
+
+func (a *APIClient) isValidNamespace(n string) (bool, error) {
+ if a == nil {
+ return false, errors.New("invalid client")
+ }
+
+ cnss := a.cachedNamespaceNames()
+ if _, ok := cnss[n]; ok {
+ return true, nil
+ }
+
+ dial, err := a.Dial()
+ if err != nil {
+ return false, err
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), a.config.CallTimeout())
+ defer cancel()
+ _, err = dial.CoreV1().Namespaces().Get(ctx, n, metav1.GetOptions{})
+ if err != nil {
+ return false, err
+ }
+ cnss[n] = struct{}{}
+ a.cache.Add("validNamespaces", cnss, cacheExpiry)
+
+ return true, nil
+}
+
// ValidNamespaceNames returns all available namespaces.
func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) {
if a == nil {
diff --git a/internal/client/config.go b/internal/client/config.go
index 76d51f4b..66990488 100644
--- a/internal/client/config.go
+++ b/internal/client/config.go
@@ -103,7 +103,7 @@ func (c *Config) CurrentClusterName() (string, error) {
if isSet(c.flags.Context) {
ct, ok = cfg.Contexts[*c.flags.Context]
if !ok {
- return "", fmt.Errorf("invalid context specified: %q", *c.flags.Context)
+ return "", fmt.Errorf("current-cluster - invalid context specified: %q", *c.flags.Context)
}
}
@@ -156,7 +156,7 @@ func (c *Config) GetContext(n string) (*api.Context, error) {
return c, nil
}
- return nil, fmt.Errorf("invalid context `%s specified", n)
+ return nil, fmt.Errorf("getcontext - invalid context specified: %q", n)
}
// Contexts fetch all available contexts.
diff --git a/internal/client/config_test.go b/internal/client/config_test.go
index f7a6f1a5..046e5dd6 100644
--- a/internal/client/config_test.go
+++ b/internal/client/config_test.go
@@ -154,7 +154,7 @@ func TestConfigGetContext(t *testing.T) {
},
"custom": {
cluster: "bozo",
- err: errors.New("invalid context `bozo specified"),
+ err: errors.New(`getcontext - invalid context specified: "bozo"`),
},
}
diff --git a/internal/config/data/dir.go b/internal/config/data/dir.go
index 6c957759..b8e5d88d 100644
--- a/internal/config/data/dir.go
+++ b/internal/config/data/dir.go
@@ -34,14 +34,9 @@ func (d Dir) Load(n string, ct *api.Context) (*Config, error) {
}
var (
- path = filepath.Join(
- d.root,
- SanitizeFileName(ct.Cluster),
- SanitizeFileName(n),
- MainConfigFile,
- )
- cfg *Config
- err error
+ path = filepath.Join(d.root, SanitizeContextSubpath(ct.Cluster, n), MainConfigFile)
+ cfg *Config
+ err error
)
if f, e := os.Stat(path); os.IsNotExist(e) || f.Size() == 0 {
log.Debug().Msgf("Context config not found! Generating... %q", path)
diff --git a/internal/config/data/helpers_test.go b/internal/config/data/helpers_test.go
index be4a6a85..ed82e458 100644
--- a/internal/config/data/helpers_test.go
+++ b/internal/config/data/helpers_test.go
@@ -12,6 +12,37 @@ import (
"github.com/stretchr/testify/assert"
)
+func TestSanitizeFileName(t *testing.T) {
+ uu := map[string]struct {
+ file, e string
+ }{
+ "empty": {},
+ "plain": {
+ file: "bumble-bee-tuna",
+ e: "bumble-bee-tuna",
+ },
+ "slash": {
+ file: "bumble/bee/tuna",
+ e: "bumble-bee-tuna",
+ },
+ "column": {
+ file: "bumble::bee:tuna",
+ e: "bumble-bee-tuna",
+ },
+ "eks": {
+ file: "arn:aws:eks:us-east-1:123456789:cluster/us-east-1-app-dev-common-eks",
+ e: "arn-aws-eks-us-east-1-123456789-cluster-us-east-1-app-dev-common-eks",
+ },
+ }
+
+ for k := range uu {
+ u := uu[k]
+ t.Run(k, func(t *testing.T) {
+ assert.Equal(t, u.e, data.SanitizeFileName(u.file))
+ })
+ }
+}
+
func TestHelperInList(t *testing.T) {
uu := []struct {
item string
diff --git a/internal/config/k9s.go b/internal/config/k9s.go
index 18e1b2da..fd8bdae6 100644
--- a/internal/config/k9s.go
+++ b/internal/config/k9s.go
@@ -61,8 +61,7 @@ func (k *K9s) Save() error {
if k.activeConfig != nil {
path := filepath.Join(
AppContextsDir,
- k.activeConfig.Context.ClusterName,
- k.activeContextName,
+ data.SanitizeContextSubpath(k.activeConfig.Context.ClusterName, k.activeContextName),
data.MainConfigFile,
)
return k.activeConfig.Save(path)
diff --git a/internal/view/app.go b/internal/view/app.go
index 9cbf742d..bbba78a2 100644
--- a/internal/view/app.go
+++ b/internal/view/app.go
@@ -100,7 +100,7 @@ func (a *App) Init(version string, rate int) error {
a.factory = watch.NewFactory(a.Conn())
ok, err := a.isValidNS(ns)
if !ok && err == nil {
- return fmt.Errorf("invalid namespace %s", ns)
+ return fmt.Errorf("app-init - invalid namespace: %q", ns)
}
a.initFactory(ns)
@@ -415,7 +415,7 @@ func (a *App) switchNS(ns string) error {
return err
}
if !ok {
- return fmt.Errorf("invalid namespace %q", ns)
+ return fmt.Errorf("switchns - invalid namespace: %q", ns)
}
if err := a.Config.SetActiveNamespace(ns); err != nil {
return err
@@ -433,7 +433,7 @@ func (a *App) isValidNS(ns string) (bool, error) {
}
if !a.Conn().IsValidNamespace(ns) {
- return false, fmt.Errorf("invalid namespace: %q", ns)
+ return false, fmt.Errorf("isvalidns - invalid namespace: %q", ns)
}
return true, nil
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index b612e4d7..342a0a8e 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -1,6 +1,6 @@
name: k9s
base: core20
-version: 'v0.30.3'
+version: 'v0.30.4'
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.