Rel v0.50.2 (#3269)
* fix#3266 dp alias fails * fix#3267 add no data flash * fix#3264 storage-class broke describe/yaml * fix#3260 po yaml crash * rel notesmine
parent
419a0ce6dc
commit
bc22b87053
2
Makefile
2
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.50.1
|
||||
VERSION ?= v0.50.2
|
||||
IMG_NAME := derailed/k9s
|
||||
IMAGE := ${IMG_NAME}:${VERSION}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
|
||||
|
||||
# Release v0.50.2
|
||||
|
||||
## 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)
|
||||
|
||||
## 5-0, 5-0 HotFix!
|
||||
|
||||
It looks like we've broken a few (more) things in the clean up process 😳
|
||||
This is what you get for trying to refresh a ~10 year old code base 🙀
|
||||
Apologizes for the `disruption in the farce`. Hopefully much happier on v0.50.2...
|
||||
Are we there yet? Crossing fingers AND toes...
|
||||
|
||||
☠️ Careful on this upgrade! 🏴☠️
|
||||
We've gone thru lots of code revamp/refactor in the v0.50.0, so mileage may vary...
|
||||
|
||||
---
|
||||
|
||||
## Resolved Issues
|
||||
|
||||
* [#3267](https://github.com/derailed/k9s/issues/3267) Show some output or message when no resources are found
|
||||
* [#3266](https://github.com/derailed/k9s/issues/3266) Command alias :dp fails with "no resource meta defined for deployments" error
|
||||
* [#3264](https://github.com/derailed/k9s/issues/3264) can't execute get(y) or describe(d) in StorageClass view
|
||||
* [#3260](https://github.com/derailed/k9s/issues/3260) yaml view of pod will crash the app (Boom!! cannot deep copy int. (Maybe??)
|
||||
|
||||
---
|
||||
<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)
|
||||
|
|
@ -22,6 +22,14 @@ func IsClusterWide(ns string) bool {
|
|||
return ns == NamespaceAll || ns == BlankNamespace || ns == ClusterScope
|
||||
}
|
||||
|
||||
func PrintNamespace(ns string) string {
|
||||
if IsAllNamespaces(ns) {
|
||||
return "all"
|
||||
}
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
// CleanseNamespace ensures all ns maps to blank.
|
||||
func CleanseNamespace(ns string) string {
|
||||
if IsAllNamespace(ns) {
|
||||
|
|
|
|||
|
|
@ -84,16 +84,14 @@ func (a *Aliases) Resolve(command string) (*client.GVR, string, bool) {
|
|||
if !ok {
|
||||
return nil, "", false
|
||||
}
|
||||
if agvr.IsCommand() {
|
||||
p := cmd.NewInterpreter(agvr.String())
|
||||
gvr, ok := a.Get(p.Cmd())
|
||||
if !ok {
|
||||
return nil, "", false
|
||||
}
|
||||
return gvr, p.Args(), true
|
||||
|
||||
p := cmd.NewInterpreter(agvr.String())
|
||||
gvr, ok := a.Get(p.Cmd())
|
||||
if !ok {
|
||||
return agvr, "", true
|
||||
}
|
||||
|
||||
return agvr, "", true
|
||||
return gvr, p.Args(), true
|
||||
}
|
||||
|
||||
// Get retrieves an alias.
|
||||
|
|
|
|||
|
|
@ -18,10 +18,13 @@ import (
|
|||
|
||||
func TestAsGVR(t *testing.T) {
|
||||
a := dao.NewAlias(makeFactory())
|
||||
a.Define(client.PodGVR, "po", "pod", "pods")
|
||||
a.Define(client.PodGVR, "po", "pipo", "pod")
|
||||
a.Define(client.PodGVR, client.PodGVR.String())
|
||||
a.Define(client.PodGVR, client.PodGVR.AsResourceName())
|
||||
a.Define(client.WkGVR, client.WkGVR.String(), "workload", "wkl")
|
||||
a.Define(client.NewGVR("pod default"), "pp")
|
||||
a.Define(client.NewGVR("pod default @fred"), "ppc")
|
||||
a.Define(client.NewGVR("pipo default"), "ppo")
|
||||
a.Define(client.NewGVR("pod default app=fred @fred"), "ppc")
|
||||
|
||||
uu := map[string]struct {
|
||||
cmd string
|
||||
|
|
@ -29,40 +32,59 @@ func TestAsGVR(t *testing.T) {
|
|||
gvr *client.GVR
|
||||
exp string
|
||||
}{
|
||||
"ok": {
|
||||
"gvr": {
|
||||
cmd: "v1/pods",
|
||||
ok: true,
|
||||
gvr: client.PodGVR,
|
||||
},
|
||||
|
||||
"r": {
|
||||
cmd: "pods",
|
||||
ok: true,
|
||||
gvr: client.PodGVR,
|
||||
},
|
||||
|
||||
"ok-short": {
|
||||
"alias1": {
|
||||
cmd: "po",
|
||||
ok: true,
|
||||
gvr: client.PodGVR,
|
||||
},
|
||||
|
||||
"alias-2": {
|
||||
cmd: "pipo",
|
||||
ok: true,
|
||||
gvr: client.PodGVR,
|
||||
},
|
||||
|
||||
"missing": {
|
||||
cmd: "zorg",
|
||||
},
|
||||
|
||||
"alias": {
|
||||
"no-args": {
|
||||
cmd: "wkl",
|
||||
ok: true,
|
||||
gvr: client.WkGVR,
|
||||
},
|
||||
|
||||
"ns-alias": {
|
||||
"ns-arg": {
|
||||
cmd: "pp",
|
||||
ok: true,
|
||||
gvr: client.PodGVR,
|
||||
exp: "default",
|
||||
},
|
||||
|
||||
"ns-inception": {
|
||||
cmd: "ppo",
|
||||
ok: true,
|
||||
gvr: client.PodGVR,
|
||||
exp: "default",
|
||||
},
|
||||
|
||||
"full-alias": {
|
||||
cmd: "ppc",
|
||||
ok: true,
|
||||
gvr: client.PodGVR,
|
||||
exp: "default @fred",
|
||||
exp: "default app=fred @fred",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"math"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
|
|
@ -85,15 +86,22 @@ func ToYAML(o runtime.Object, showManaged bool) (string, error) {
|
|||
if o == nil {
|
||||
return "", errors.New("no object to yamlize")
|
||||
}
|
||||
u, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("expecting unstructured but got %T", o)
|
||||
}
|
||||
if u.Object == nil {
|
||||
return "", fmt.Errorf("expecting unstructured object but got nil")
|
||||
}
|
||||
|
||||
mm := u.Object
|
||||
var (
|
||||
buff bytes.Buffer
|
||||
p printers.YAMLPrinter
|
||||
)
|
||||
if !showManaged {
|
||||
o = o.DeepCopyObject()
|
||||
uo := o.(*unstructured.Unstructured).Object
|
||||
if meta, ok := uo["metadata"].(map[string]any); ok {
|
||||
mm = maps.Clone(mm)
|
||||
if meta, ok := mm["metadata"].(map[string]any); ok {
|
||||
delete(meta, "managedFields")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -331,6 +331,9 @@ func loadPreferred(f Factory, m ResourceMetas) error {
|
|||
if !isStandardGroup(r.GroupVersion) {
|
||||
res.Categories = append(res.Categories, crdCat)
|
||||
}
|
||||
if isScalable(gvr) {
|
||||
res.Categories = append(res.Categories, scaleCat)
|
||||
}
|
||||
m[gvr] = &res
|
||||
}
|
||||
}
|
||||
|
|
@ -342,6 +345,12 @@ func isStandardGroup(gv string) bool {
|
|||
return stdGroups.Has(gv) || strings.Contains(gv, ".k8s.io")
|
||||
}
|
||||
|
||||
func isScalable(gvr *client.GVR) bool {
|
||||
ss := sets.New(client.DpGVR, client.StsGVR)
|
||||
|
||||
return ss.Has(gvr)
|
||||
}
|
||||
|
||||
var deprecatedGVRs = sets.New(
|
||||
client.NewGVR("v1/events"),
|
||||
client.NewGVR("extensions/v1beta1/ingresses"),
|
||||
|
|
|
|||
|
|
@ -6,12 +6,9 @@ package dao
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/slogs"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
|
@ -72,7 +69,6 @@ func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ti := time.Now()
|
||||
o, err := c.Get().
|
||||
SetHeader("Accept", header).
|
||||
Param("includeObject", includeObject).
|
||||
|
|
@ -86,7 +82,6 @@ func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
slog.Debug("Q Time", slogs.Elapsed, time.Since(ti))
|
||||
|
||||
namespaced := true
|
||||
if res, e := MetaAccess.MetaFor(t.gvr); e == nil && !res.Namespaced {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ var Registry = map[string]ResourceMeta{
|
|||
Renderer: new(render.Container),
|
||||
TreeRenderer: new(xray.Container),
|
||||
},
|
||||
client.ScGVR.String(): {
|
||||
client.ScnGVR.String(): {
|
||||
DAO: new(dao.ImageScan),
|
||||
Renderer: new(render.ImageScan),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ const initRefreshRate = 300 * time.Millisecond
|
|||
|
||||
// TableListener represents a table model listener.
|
||||
type TableListener interface {
|
||||
// TableNoData notifies listener no data was found.
|
||||
TableNoData(*model1.TableData)
|
||||
|
||||
// TableDataChanged notifies the model data changed.
|
||||
TableDataChanged(*model1.TableData)
|
||||
|
||||
|
|
@ -232,7 +235,12 @@ func (t *Table) refresh(ctx context.Context) error {
|
|||
if err := t.reconcile(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
t.fireTableChanged(t.Peek())
|
||||
data := t.Peek()
|
||||
if data.RowCount() == 0 {
|
||||
t.fireNoData(data)
|
||||
} else {
|
||||
t.fireTableChanged(data)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -292,6 +300,17 @@ func (t *Table) fireTableChanged(data *model1.TableData) {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *Table) fireNoData(data *model1.TableData) {
|
||||
var ll []TableListener
|
||||
t.mx.RLock()
|
||||
ll = t.listeners
|
||||
t.mx.RUnlock()
|
||||
|
||||
for _, l := range ll {
|
||||
l.TableNoData(data)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Table) fireTableLoadFailed(err error) {
|
||||
var ll []TableListener
|
||||
t.mx.RLock()
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ type tableListener struct {
|
|||
count, errs int
|
||||
}
|
||||
|
||||
func (*tableListener) TableNoData(*model1.TableData) {}
|
||||
|
||||
func (l *tableListener) TableDataChanged(*model1.TableData) {
|
||||
l.count++
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
|
|
@ -39,6 +40,7 @@ type Browser struct {
|
|||
cancelFn context.CancelFunc
|
||||
mx sync.RWMutex
|
||||
updating bool
|
||||
firstView atomic.Int32
|
||||
}
|
||||
|
||||
// NewBrowser returns a new browser.
|
||||
|
|
@ -279,6 +281,36 @@ func (b *Browser) Aliases() sets.Set[string] {
|
|||
// ----------------------------------------------------------------------------
|
||||
// Model Protocol...
|
||||
|
||||
// TableNoData notifies view no data is available.
|
||||
func (b *Browser) TableNoData(data *model1.TableData) {
|
||||
var cancel context.CancelFunc
|
||||
b.mx.RLock()
|
||||
cancel = b.cancelFn
|
||||
b.mx.RUnlock()
|
||||
|
||||
if !b.app.ConOK() || cancel == nil || !b.app.IsRunning() {
|
||||
return
|
||||
}
|
||||
if b.firstView.Load() == 0 {
|
||||
b.firstView.Add(1)
|
||||
return
|
||||
}
|
||||
|
||||
cdata := b.Update(data, b.app.Conn().HasMetrics())
|
||||
b.app.QueueUpdateDraw(func() {
|
||||
if b.getUpdating() {
|
||||
return
|
||||
}
|
||||
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.refreshActions()
|
||||
b.UpdateUI(cdata, data)
|
||||
})
|
||||
}
|
||||
|
||||
// TableDataChanged notifies view new data is available.
|
||||
func (b *Browser) TableDataChanged(data *model1.TableData) {
|
||||
var cancel context.CancelFunc
|
||||
|
|
@ -297,6 +329,9 @@ func (b *Browser) TableDataChanged(data *model1.TableData) {
|
|||
}
|
||||
b.setUpdating(true)
|
||||
defer b.setUpdating(false)
|
||||
if b.GetColumnCount() == 0 {
|
||||
b.app.Flash().Infof("Viewing %s in namespace %s", b.GVR(), client.PrintNamespace(b.GetNamespace()))
|
||||
}
|
||||
b.refreshActions()
|
||||
b.UpdateUI(cdata, data)
|
||||
})
|
||||
|
|
@ -488,7 +523,7 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
b.setNamespace(ns)
|
||||
b.app.Flash().Infof("Viewing namespace `%s`...", ns)
|
||||
b.app.Flash().Infof("Viewing %s in namespace `%s`...", b.GVR(), client.PrintNamespace(ns))
|
||||
b.refresh()
|
||||
b.UpdateTitle()
|
||||
b.SelectRow(1, 0, true)
|
||||
|
|
@ -528,7 +563,7 @@ func (b *Browser) defaultContext() context.Context {
|
|||
}
|
||||
|
||||
func (b *Browser) refreshActions() {
|
||||
if b.App().Content.Top() != nil && b.App().Content.Top().Name() != b.Name() {
|
||||
if top := b.App().Content.Top(); top != nil && top.Name() != b.Name() {
|
||||
return
|
||||
}
|
||||
aa := ui.NewKeyActionsFromMap(ui.KeyMap{
|
||||
|
|
|
|||
|
|
@ -344,7 +344,6 @@ func (c *Command) exec(p *cmd.Interpreter, gvr *client.GVR, comp model.Component
|
|||
}
|
||||
comp.SetCommand(p)
|
||||
|
||||
c.app.Flash().Infof("Viewing %s...", gvr)
|
||||
if clearStack {
|
||||
cmd := contextRX.ReplaceAllString(p.GetLine(), "")
|
||||
c.app.Config.SetActiveView(cmd)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func miscViewers(vv MetaViewers) {
|
|||
vv[client.CoGVR] = MetaViewer{
|
||||
viewerFn: NewContainer,
|
||||
}
|
||||
vv[client.ScGVR] = MetaViewer{
|
||||
vv[client.ScnGVR] = MetaViewer{
|
||||
viewerFn: NewImageScan,
|
||||
}
|
||||
vv[client.PfGVR] = MetaViewer{
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ func (v *VulnerabilityExtender) bindKeys(aa *ui.KeyActions) {
|
|||
}
|
||||
|
||||
func (v *VulnerabilityExtender) showVulCmd(*tcell.EventKey) *tcell.EventKey {
|
||||
isv := NewImageScan(client.ScGVR)
|
||||
isv := NewImageScan(client.ScnGVR)
|
||||
isv.SetContextFn(v.selContext)
|
||||
if err := v.App().inject(isv, false); err != nil {
|
||||
v.App().Flash().Err(err)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name: k9s
|
||||
base: core22
|
||||
version: 'v0.50.1'
|
||||
version: 'v0.50.2'
|
||||
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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue