diff --git a/Makefile b/Makefile
index 65011237..9154aba6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
NAME := k9s
-VERSION ?= v0.50.11
+VERSION ?= v0.50.12
PACKAGE := github.com/derailed/$(NAME)
OUTPUT_BIN ?= execs/${NAME}
GO_FLAGS ?=
diff --git a/change_logs/release_v0.50.12.md b/change_logs/release_v0.50.12.md
new file mode 100644
index 00000000..eadc26ef
--- /dev/null
+++ b/change_logs/release_v0.50.12.md
@@ -0,0 +1,29 @@
+
+
+# Release v0.50.12
+
+## 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/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)
+
+## Maintenance Release!
+
+## Resolved Issues
+
+* [#3570](https://github.com/derailed/k9s/issues/3570) 0.50.11 could not display any resources
+* [#3562](https://github.com/derailed/k9s/issues/3562) Can't delete namespace
+* [#3547](https://github.com/derailed/k9s/issues/3547) Error message from admission controller
+
+---
+
© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#
\ No newline at end of file
diff --git a/internal/client/client.go b/internal/client/client.go
index a012781a..125efca4 100644
--- a/internal/client/client.go
+++ b/internal/client/client.go
@@ -184,7 +184,7 @@ func (a *APIClient) CanI(ns string, gvr *GVR, name string, verbs []string) (auth
slogs.Verb, verbs,
)
if resp != nil {
- clog.Debug("[CAN] reps",
+ clog.Debug("[CAN] response",
slogs.AuthStatus, resp.Status.Allowed,
slogs.AuthReason, resp.Status.Reason,
)
@@ -196,7 +196,7 @@ func (a *APIClient) CanI(ns string, gvr *GVR, name string, verbs []string) (auth
}
if !resp.Status.Allowed {
a.cache.Add(key, false, cacheExpiry)
- return auth, fmt.Errorf("`%s access denied for user on %q:%s", v, ns, gvr)
+ return auth, fmt.Errorf("(%s) access denied for user on resource %q:%s in namespace %q", v, name, gvr, ns)
}
}
auth = true
diff --git a/internal/model/flash.go b/internal/model/flash.go
index b98669c2..bb363536 100644
--- a/internal/model/flash.go
+++ b/internal/model/flash.go
@@ -14,7 +14,7 @@ import (
const (
// DefaultFlashDelay sets the flash clear delay.
- DefaultFlashDelay = 3 * time.Second
+ DefaultFlashDelay = 6 * time.Second
// FlashInfo represents an info message.
FlashInfo FlashLevel = iota
diff --git a/internal/view/browser.go b/internal/view/browser.go
index 90d8dd02..57e44072 100644
--- a/internal/view/browser.go
+++ b/internal/view/browser.go
@@ -302,8 +302,8 @@ func (b *Browser) TableNoData(mdata *model1.TableData) {
if !b.app.ConOK() || cancel == nil || !b.app.IsRunning() {
return
}
- // Skip warning on first view or if table data is empty (likely during initialization)
- if b.firstView.Load() == 0 || mdata.Empty() {
+ // Skip warning on first view (likely during initialization)
+ if b.firstView.Load() == 0 || mdata.HeaderCount() == 0 {
b.firstView.Add(1)
return
}
@@ -316,7 +316,7 @@ func (b *Browser) TableNoData(mdata *model1.TableData) {
b.setUpdating(true)
defer b.setUpdating(false)
if b.GetColumnCount() == 0 {
- b.app.Flash().Warnf("No resources found for %s in namespace %s", b.GVR(), client.PrintNamespace(b.GetNamespace()))
+ b.app.Flash().Warnf("No resources found for %s in %q namespace", b.GVR(), client.PrintNamespace(b.GetNamespace()))
}
b.refreshActions()
b.UpdateUI(cdata, mdata)
diff --git a/internal/view/container.go b/internal/view/container.go
index 26135f8e..61f0f334 100644
--- a/internal/view/container.go
+++ b/internal/view/container.go
@@ -164,9 +164,21 @@ func (c *Container) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
+ var err error
c.Stop()
- defer c.Start()
- shellIn(c.App(), c.GetTable().Path, path)
+ defer func() {
+ c.Start()
+ if err != nil {
+ c.App().QueueUpdate(func() {
+ if err != nil {
+ c.App().Flash().Errf("Shell exec failed: %s", err)
+ }
+ })
+
+ c.App().Flash().Err(err)
+ }
+ }()
+ err = shellIn(c.App(), c.GetTable().Path, path)
return nil
}
diff --git a/internal/view/exec.go b/internal/view/exec.go
index 60d79242..38eb60d0 100644
--- a/internal/view/exec.go
+++ b/internal/view/exec.go
@@ -587,7 +587,7 @@ func pipe(_ context.Context, opts *shellOpts, statusChan chan<- string, w, e *by
close(statusChan)
if err != nil {
- err = fmt.Errorf("command failed. Check logs: %w", err)
+ err = fmt.Errorf("command failed. Check k9s logs: %w", err)
}
return err
diff --git a/internal/view/pod.go b/internal/view/pod.go
index 5bc6639c..9b3417bf 100644
--- a/internal/view/pod.go
+++ b/internal/view/pod.go
@@ -393,28 +393,33 @@ func containerShellIn(a *App, comp model.Component, path, co string) error {
}
func resumeShellIn(a *App, c model.Component, path, co string) {
+ var err error
c.Stop()
- defer c.Start()
+ defer func() {
+ c.Start()
+ a.QueueUpdate(func() {
+ if err != nil {
+ a.Flash().Errf("Shell exec failed: %s", err)
+ }
+ })
+ }()
- shellIn(a, path, co)
+ err = shellIn(a, path, co)
}
-func shellIn(a *App, fqn, co string) {
+func shellIn(a *App, fqn, co string) error {
platform, err := getPodOS(a.factory, fqn)
if err != nil {
- slog.Warn("OS detect failed", slogs.Error, err)
+ return err
}
- args := computeShellArgs(fqn, co, a.Conn().Config().Flags(), platform)
+ args := computeShellArgs(fqn, co, a.Conn().Config().Flags(), platform)
c := color.New(color.BgGreen).Add(color.FgBlack).Add(color.Bold)
- err = runK(a, &shellOpts{
+ return runK(a, &shellOpts{
clear: true,
banner: c.Sprintf(bannerFmt, fqn, co),
args: args},
)
- if err != nil {
- a.Flash().Errf("Shell exec failed: %s", err)
- }
}
func containerAttachIn(a *App, comp model.Component, path, co string) error {
diff --git a/internal/watch/factory.go b/internal/watch/factory.go
index e65d1eb1..408239fb 100644
--- a/internal/watch/factory.go
+++ b/internal/watch/factory.go
@@ -201,7 +201,11 @@ func (f *Factory) isClusterWide() bool {
// CanForResource return an informer is user has access.
func (f *Factory) CanForResource(ns string, gvr *client.GVR, verbs []string) (informers.GenericInformer, error) {
- auth, err := f.Client().CanI(ns, gvr, "", verbs)
+ var resName string
+ if gvr == client.NsGVR {
+ resName = ns
+ }
+ auth, err := f.Client().CanI(ns, gvr, resName, verbs)
if err != nil {
return nil, err
}
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 96dc0f9c..6154f8b1 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -1,6 +1,6 @@
name: k9s
base: core22
-version: 'v0.50.11'
+version: 'v0.50.12'
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.