Skip to content

Commit

Permalink
Rel v0.40.7 (#3188)
Browse files Browse the repository at this point in the history
* [FEAT] add readonly indicator

* [FIX] Hosed cust view loading

* rel notes
  • Loading branch information
derailed authored Mar 10, 2025
1 parent 1244cc5 commit 08b8efa
Show file tree
Hide file tree
Showing 24 changed files with 127 additions and 42 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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.40.6
VERSION ?= v0.40.7
IMG_NAME := derailed/k9s
IMAGE := ${IMG_NAME}:${VERSION}

Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -695,11 +695,17 @@ views:
- MEM/RL|S # => 🌚 Overrides std resource default wide attribute via `S` for `Show`
- '%MEM/R|' # => NOTE! column names with non alpha names need to be quoted as columns must be strings!
v1/pods@fred: # => 🌚 New v0.40.6! Customize columns for a given resource and namespace!
v1/pods@fred: # => 🌚 New v0.40.6! Customize columns for a given resource and namespace!
columns:
- AGE
- NAMESPACE|WR
v1/pods@kube*: # => 🌚 New v0.40.6! You can also specify a namespace using a regular expression.
columns:
- AGE
- NAMESPACE|WR
v1/services:
columns:
- AGE
Expand Down
45 changes: 45 additions & 0 deletions change_logs/release_v0.40.7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>

# Release v0.40.7

## 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!

🙀 Hoy! Hosed custom view loading in v0.40.6...

## Videos Are In The Can!

Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...

* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)

---

## Resolved Issues

---

## 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!!

* [#3186](https://github.com/derailed/k9s/pull/3186) fix: allow absolute paths for the 'dir' command

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
5 changes: 5 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ func NewConfig(ks data.KubeSettings) *Config {
}
}

// IsReadOnly returns true if K9s is running in read-only mode.
func (c *Config) IsReadOnly() bool {
return c.K9s.IsReadOnly()
}

// ActiveClusterName returns the corresponding cluster name.
func (c *Config) ActiveClusterName(contextName string) (string, error) {
ct, err := c.settings.GetContext(contextName)
Expand Down
23 changes: 14 additions & 9 deletions internal/config/views.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,28 +142,33 @@ func (v *CustomView) fireConfigChanged() {

func (v *CustomView) getVS(gvr, ns string) *ViewSetting {
k := gvr
if ns != "" {
k += "@" + ns
}

for key := range maps.Keys(v.Views) {
kk := slices.Collect(maps.Keys(v.Views))
slices.SortFunc(kk, func(s1, s2 string) int {
return strings.Compare(s1, s2)
})
slices.Reverse(kk)
for _, key := range kk {
if !strings.HasPrefix(key, gvr) {
continue
}

switch {
case key == k:
vs := v.Views[key]
return &vs
case strings.Contains(key, "@"):
tt := strings.Split(key, "@")
if len(tt) != 2 {
break
}
if rx, err := regexp.Compile(tt[1]); err == nil && rx.MatchString(k) {
nsk := gvr
if ns != "" {
nsk += "@" + ns
}
if rx, err := regexp.Compile(tt[1]); err == nil && rx.MatchString(nsk) {
vs := v.Views[key]
return &vs
}
case key == k:
vs := v.Views[key]
return &vs
}
}

Expand Down
3 changes: 3 additions & 0 deletions internal/config/views_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ func TestCustomView_getVS(t *testing.T) {
"toast-no-ns": {
gvr: "v1/pods",
ns: "zorg",
e: &ViewSetting{
Columns: []string{"NAMESPACE", "NAME", "AGE", "IP"},
},
},

"toast-no-res": {
Expand Down
36 changes: 24 additions & 12 deletions internal/ui/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render"
Expand Down Expand Up @@ -54,6 +53,7 @@ type Table struct {
hasMetrics bool
ctx context.Context
mx sync.RWMutex
readOnly bool
}

// NewTable returns a new table view.
Expand All @@ -72,6 +72,13 @@ func NewTable(gvr client.GVR) *Table {
}
}

func (t *Table) SetReadOnly(ro bool) {
t.mx.Lock()
defer t.mx.Unlock()

t.readOnly = ro
}

func (t *Table) setSortCol(sc model1.SortColumn) {
t.mx.Lock()
defer t.mx.Unlock()
Expand Down Expand Up @@ -297,17 +304,12 @@ func (t *Table) UpdateUI(cdata, data *model1.TableData) {
fg := t.styles.Table().Header.FgColor.Color()
bg := t.styles.Table().Header.BgColor.Color()

var isNamespaced bool
if m, err := dao.MetaAccess.MetaFor(t.GVR()); err == nil {
isNamespaced = m.Namespaced
}

var col int
for _, h := range cdata.Header() {
if h.Hide || (!t.wide && h.Wide) {
continue
}
if h.Name == "NAMESPACE" && (!t.GetModel().ClusterWide() || !isNamespaced) {
if h.Name == "NAMESPACE" && !t.GetModel().ClusterWide() {
continue
}
if h.MX && !t.hasMetrics {
Expand All @@ -333,7 +335,7 @@ func (t *Table) UpdateUI(cdata, data *model1.TableData) {
slog.Error("Unable to find original row event", slogs.RowID, re.Row.ID)
return true
}
t.buildRow(row+1, re, ore, cdata.Header(), pads, isNamespaced)
t.buildRow(row+1, re, ore, cdata.Header(), pads)

return true
})
Expand All @@ -342,7 +344,7 @@ func (t *Table) UpdateUI(cdata, data *model1.TableData) {
t.UpdateTitle()
}

func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads MaxyPad, isNamespaced bool) {
func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads MaxyPad) {
color := model1.DefaultColorer
if t.colorerFn != nil {
color = t.colorerFn
Expand All @@ -364,7 +366,7 @@ func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads M
continue
}

if h[c].Name == "NAMESPACE" && (!t.GetModel().ClusterWide() || !isNamespaced) {
if h[c].Name == "NAMESPACE" && !t.GetModel().ClusterWide() {
continue
}
if h[c].MX && !t.hasMetrics {
Expand Down Expand Up @@ -532,11 +534,12 @@ func (t *Table) styleTitle() string {
if t.Extras != "" {
ns = t.Extras
}

var title string
if ns == client.ClusterScope {
title = SkinTitle(fmt.Sprintf(TitleFmt, t.gvr, render.AsThousands(rc)), t.styles.Frame())
title = SkinTitle(fmt.Sprintf(TitleFmt, ROIndicator(t.readOnly), t.gvr, render.AsThousands(rc)), t.styles.Frame())
} else {
title = SkinTitle(fmt.Sprintf(NSTitleFmt, t.gvr, ns, render.AsThousands(rc)), t.styles.Frame())
title = SkinTitle(fmt.Sprintf(NSTitleFmt, ROIndicator(t.readOnly), t.gvr, ns, render.AsThousands(rc)), t.styles.Frame())
}

buff := t.cmdBuff.GetText()
Expand All @@ -552,3 +555,12 @@ func (t *Table) styleTitle() string {

return title + SkinTitle(fmt.Sprintf(SearchFmt, buff), t.styles.Frame())
}

// ROIndicator returns an icon showing whether the session is in readonly mode or not.
func ROIndicator(ro bool) string {
if ro {
return LockedIC
}

return UnlockedIC
}
4 changes: 2 additions & 2 deletions internal/ui/table_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ const (
SearchFmt = "<[filter:bg:r]/%s[fg:bg:-]> "

// NSTitleFmt represents a namespaced view title.
NSTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] "
NSTitleFmt = " %s [fg:bg:b]%s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] "

// TitleFmt represents a standard view title.
TitleFmt = "[fg:bg:b] %s[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] "
TitleFmt = " %s [fg:bg:b]%s[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] "

descIndicator = "↓"
ascIndicator = "↑"
Expand Down
8 changes: 8 additions & 0 deletions internal/ui/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)

const (
// UnlockedIC represents an unlocked icon.
UnlockedIC = "🔓"

// LockedIC represents a locked icon.
LockedIC = "🔒"
)

// Namespaceable represents a namespaceable model.
type Namespaceable interface {
// ClusterWide returns true if the model represents resource in all namespaces.
Expand Down
2 changes: 1 addition & 1 deletion internal/view/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func pluginActions(r Runner, aa *ui.KeyActions) error {
var (
errs error
aliases = r.Aliases()
ro = r.App().Config.K9s.IsReadOnly()
ro = r.App().Config.IsReadOnly()
)
for k, plugin := range pp.Plugins {
if !inScope(plugin.Scopes, aliases) {
Expand Down
3 changes: 2 additions & 1 deletion internal/view/browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func (b *Browser) Init(ctx context.Context) error {
if b.App().IsRunning() {
b.app.CmdBuff().Reset()
}
b.Table.SetReadOnly(b.app.Config.IsReadOnly())

b.bindKeys(b.Actions())
for _, f := range b.bindKeysFn {
Expand Down Expand Up @@ -537,7 +538,7 @@ func (b *Browser) refreshActions() {

if b.app.ConOK() {
b.namespaceActions(aa)
if !b.app.Config.K9s.IsReadOnly() {
if !b.app.Config.IsReadOnly() {
if client.Can(b.meta.Verbs, "edit") {
aa.Add(ui.KeyE, ui.NewKeyActionWithOpts("Edit", b.editCmd,
ui.ActionOpts{
Expand Down
2 changes: 1 addition & 1 deletion internal/view/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (c *Container) bindDangerousKeys(aa *ui.KeyActions) {
func (c *Container) bindKeys(aa *ui.KeyActions) {
aa.Delete(tcell.KeyCtrlSpace, ui.KeySpace)

if !c.App().Config.K9s.IsReadOnly() {
if !c.App().Config.IsReadOnly() {
c.bindDangerousKeys(aa)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/view/dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (d *Dir) bindKeys(aa *ui.KeyActions) {
// !!BOZO!! Lame!
aa.Delete(ui.KeyShiftA, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace)
aa.Delete(tcell.KeyCtrlW, tcell.KeyCtrlL, tcell.KeyCtrlD, tcell.KeyCtrlZ)
if !d.App().Config.K9s.IsReadOnly() {
if !d.App().Config.IsReadOnly() {
d.bindDangerousKeys(aa)
}
aa.Bulk(ui.KeyMap{
Expand Down
2 changes: 1 addition & 1 deletion internal/view/helm_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (h *History) HistoryContext(ctx context.Context) context.Context {
}

func (h *History) bindKeys(aa *ui.KeyActions) {
if !h.App().Config.K9s.IsReadOnly() {
if !h.App().Config.IsReadOnly() {
h.bindDangerousKeys(aa)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/view/image_extender.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func NewImageExtender(r ResourceViewer) ResourceViewer {
}

func (s *ImageExtender) bindKeys(aa *ui.KeyActions) {
if s.App().Config.K9s.IsReadOnly() {
if s.App().Config.IsReadOnly() {
return
}
aa.Add(ui.KeyI, ui.NewKeyAction("Set Image", s.setImageCmd, false))
Expand Down
2 changes: 1 addition & 1 deletion internal/view/live_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func (v *LiveView) bindKeys() {
tcell.KeyDelete: ui.NewSharedKeyAction("Erase", v.eraseCmd, false),
})

if !v.app.Config.K9s.IsReadOnly() {
if !v.app.Config.IsReadOnly() {
v.actions.Add(ui.KeyE, ui.NewKeyAction("Edit", v.editCmd, true))
}
if v.title == yamlAction {
Expand Down
2 changes: 1 addition & 1 deletion internal/view/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (n *Node) bindDangerousKeys(aa *ui.KeyActions) {
}

func (n *Node) bindKeys(aa *ui.KeyActions) {
if !n.App().Config.K9s.IsReadOnly() {
if !n.App().Config.IsReadOnly() {
n.bindDangerousKeys(aa)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/view/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (p *Pod) bindDangerousKeys(aa *ui.KeyActions) {
}

func (p *Pod) bindKeys(aa *ui.KeyActions) {
if !p.App().Config.K9s.IsReadOnly() {
if !p.App().Config.IsReadOnly() {
p.bindDangerousKeys(aa)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/view/restart_extender.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func NewRestartExtender(v ResourceViewer) ResourceViewer {

// BindKeys creates additional menu actions.
func (r *RestartExtender) bindKeys(aa *ui.KeyActions) {
if r.App().Config.K9s.IsReadOnly() {
if r.App().Config.IsReadOnly() {
return
}
aa.Add(ui.KeyR, ui.NewKeyActionWithOpts("Restart", r.restartCmd,
Expand Down
4 changes: 2 additions & 2 deletions internal/view/sanitizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,9 +419,9 @@ func (s *Sanitizer) styleTitle() string {

var title string
if ns == client.ClusterScope {
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, render.AsThousands(int64(s.Count))), s.app.Styles.Frame())
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, ui.ROIndicator(s.app.Config.IsReadOnly()), base, render.AsThousands(int64(s.Count))), s.app.Styles.Frame())
} else {
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, render.AsThousands(int64(s.Count))), s.app.Styles.Frame())
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, ui.ROIndicator(s.app.Config.IsReadOnly()), base, ns, render.AsThousands(int64(s.Count))), s.app.Styles.Frame())
}

buff := s.CmdBuff().GetText()
Expand Down
2 changes: 1 addition & 1 deletion internal/view/scale_extender.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func NewScaleExtender(r ResourceViewer) ResourceViewer {
}

func (s *ScaleExtender) bindKeys(aa *ui.KeyActions) {
if s.App().Config.K9s.IsReadOnly() {
if s.App().Config.IsReadOnly() {
return
}

Expand Down
2 changes: 1 addition & 1 deletion internal/view/workload.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (w *Workload) bindDangerousKeys(aa *ui.KeyActions) {
}

func (w *Workload) bindKeys(aa *ui.KeyActions) {
if !w.App().Config.K9s.IsReadOnly() {
if !w.App().Config.IsReadOnly() {
w.bindDangerousKeys(aa)
}

Expand Down
Loading

0 comments on commit 08b8efa

Please sign in to comment.