diff --git a/Makefile b/Makefile index a48e9de2..4435d51d 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PACKAGE := github.com/derailed/$(NAME) GIT_REV ?= $(shell git rev-parse --short HEAD) SOURCE_DATE_EPOCH ?= $(shell date +%s) DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") -VERSION ?= v0.25.20 +VERSION ?= v0.25.21 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.25.21.md b/change_logs/release_v0.25.21.md new file mode 100644 index 00000000..15add9ac --- /dev/null +++ b/change_logs/release_v0.25.21.md @@ -0,0 +1,33 @@ + + +# Release v0.25.21 + +## 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 this repo!! + +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 + +--- + +## Resolved Issues + +* [Issue #1634](https://github.com/derailed/k9s/issues/1634) Namespace view all has the age field in strange format +* [Issue #1633](https://github.com/derailed/k9s/issues/1633) Nodes sort by age has wrong order + +## Resolved PR + +* [PR #1632](https://github.com/derailed/k9s/pull/1632) Fix delete dialog dropdown styling +* [PR #1629](https://github.com/derailed/k9s/pull/1629) Fix reference to base image in dockerfile +* [PR #1627](https://github.com/derailed/k9s/pull/1627) Fix TestToAge +* [PR #1624](https://github.com/derailed/k9s/pull/1624) Change makefile version + +--- + + © 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/go.mod b/go.mod index 84080044..bcf40445 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect - github.com/containerd/containerd v1.6.3 // indirect + github.com/containerd/containerd v1.6.6 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.11+incompatible // indirect @@ -147,7 +147,7 @@ require ( golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 7dd07a61..cd67af76 100644 --- a/go.sum +++ b/go.sum @@ -83,7 +83,7 @@ github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFP github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE= github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= -github.com/Microsoft/hcsshim v0.9.2 h1:wB06W5aYFfUB3IvootYAY2WnOmIdgPGfqSI6tufQNnY= +github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -156,8 +156,8 @@ github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= -github.com/containerd/containerd v1.6.3 h1:JfgUEIAH07xDWk6kqz0P3ArZt+KJ9YeihSC9uyFtSKg= -github.com/containerd/containerd v1.6.3/go.mod h1:gCVGrYRYFm2E8GmuUIbj/NGD7DLZQLzSJQazjVKDOig= +github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= +github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= @@ -999,8 +999,9 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210406210042-72f3dc4e9b72/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/internal/dao/helm.go b/internal/dao/helm.go index c8c7ed63..e4c07dd2 100644 --- a/internal/dao/helm.go +++ b/internal/dao/helm.go @@ -23,15 +23,16 @@ var ( // Helm represents a helm chart. type Helm struct { NonResource + cfg *action.Configuration + ns string } // List returns a collection of resources. -func (c *Helm) List(ctx context.Context, ns string) ([]runtime.Object, error) { - cfg, err := c.EnsureHelmConfig(ns) +func (h *Helm) List(ctx context.Context, ns string) ([]runtime.Object, error) { + cfg, err := h.EnsureHelmConfig(ns) if err != nil { return nil, err } - rr, err := action.NewList(cfg).Run() if err != nil { return nil, err @@ -46,9 +47,9 @@ func (c *Helm) List(ctx context.Context, ns string) ([]runtime.Object, error) { } // Get returns a resource. -func (c *Helm) Get(_ context.Context, path string) (runtime.Object, error) { +func (h *Helm) Get(_ context.Context, path string) (runtime.Object, error) { ns, n := client.Namespaced(path) - cfg, err := c.EnsureHelmConfig(ns) + cfg, err := h.EnsureHelmConfig(ns) if err != nil { return nil, err } @@ -61,9 +62,9 @@ func (c *Helm) Get(_ context.Context, path string) (runtime.Object, error) { } // GetValues returns values for a release -func (c *Helm) GetValues(path string, allValues bool) ([]byte, error) { +func (h *Helm) GetValues(path string, allValues bool) ([]byte, error) { ns, n := client.Namespaced(path) - cfg, err := c.EnsureHelmConfig(ns) + cfg, err := h.EnsureHelmConfig(ns) if err != nil { return nil, err } @@ -78,9 +79,9 @@ func (c *Helm) GetValues(path string, allValues bool) ([]byte, error) { } // Describe returns the chart notes. -func (c *Helm) Describe(path string) (string, error) { +func (h *Helm) Describe(path string) (string, error) { ns, n := client.Namespaced(path) - cfg, err := c.EnsureHelmConfig(ns) + cfg, err := h.EnsureHelmConfig(ns) if err != nil { return "", err } @@ -93,9 +94,9 @@ func (c *Helm) Describe(path string) (string, error) { } // ToYAML returns the chart manifest. -func (c *Helm) ToYAML(path string, showManaged bool) (string, error) { +func (h *Helm) ToYAML(path string, showManaged bool) (string, error) { ns, n := client.Namespaced(path) - cfg, err := c.EnsureHelmConfig(ns) + cfg, err := h.EnsureHelmConfig(ns) if err != nil { return "", err } @@ -108,9 +109,9 @@ func (c *Helm) ToYAML(path string, showManaged bool) (string, error) { } // Delete uninstall a Helm. -func (c *Helm) Delete(path string, _ *metav1.DeletionPropagation, force bool) error { +func (h *Helm) Delete(path string, _ *metav1.DeletionPropagation, force bool) error { ns, n := client.Namespaced(path) - cfg, err := c.EnsureHelmConfig(ns) + cfg, err := h.EnsureHelmConfig(ns) if err != nil { return err } @@ -128,12 +129,15 @@ func (c *Helm) Delete(path string, _ *metav1.DeletionPropagation, force bool) er } // EnsureHelmConfig return a new configuration. -func (c *Helm) EnsureHelmConfig(ns string) (*action.Configuration, error) { - cfg := new(action.Configuration) - if err := cfg.Init(c.Client().Config().Flags(), ns, os.Getenv("HELM_DRIVER"), helmLogger); err != nil { +func (h *Helm) EnsureHelmConfig(ns string) (*action.Configuration, error) { + if h.cfg != nil && h.ns == ns { + return h.cfg, nil + } + h.cfg = new(action.Configuration) + if err := h.cfg.Init(h.Client().Config().Flags(), ns, os.Getenv("HELM_DRIVER"), helmLogger); err != nil { return nil, err } - return cfg, nil + return h.cfg, nil } func helmLogger(s string, args ...interface{}) { diff --git a/internal/render/header.go b/internal/render/header.go index b17a0ec2..91eab1ae 100644 --- a/internal/render/header.go +++ b/internal/render/header.go @@ -150,6 +150,7 @@ func (h Header) IsMetricsCol(col int) bool { if col < 0 || col >= len(h) { return false } + return h[col].MX } diff --git a/internal/render/helpers_test.go b/internal/render/helpers_test.go index 9d2acd24..1ec2e85e 100644 --- a/internal/render/helpers_test.go +++ b/internal/render/helpers_test.go @@ -96,10 +96,6 @@ func TestToAge(t *testing.T) { t: time.Time{}, e: UnknownValue, }, - "good": { - t: testTime().Add(-10 * time.Second), - e: "3y197d", - }, } for k := range uu { diff --git a/internal/render/row.go b/internal/render/row.go index a923a049..5962c24a 100644 --- a/internal/render/row.go +++ b/internal/render/row.go @@ -177,14 +177,18 @@ func (s RowSorter) Swap(i, j int) { func (s RowSorter) Less(i, j int) bool { v1, v2 := s.Rows[i].Fields[s.Index], s.Rows[j].Fields[s.Index] id1, id2 := s.Rows[i].ID, s.Rows[j].ID - return Less(s.Asc, s.IsNumber, s.IsDuration, id1, id2, v1, v2) + less := Less(s.IsNumber, s.IsDuration, id1, id2, v1, v2) + if s.Asc { + return less + } + return !less } // ---------------------------------------------------------------------------- // Helpers... // Less return true if c1 < c2. -func Less(asc, isNumber, isDuration bool, id1, id2, v1, v2 string) bool { +func Less(isNumber, isDuration bool, id1, id2, v1, v2 string) bool { var less bool switch { case isNumber: @@ -196,12 +200,9 @@ func Less(asc, isNumber, isDuration bool, id1, id2, v1, v2 string) bool { default: less = sortorder.NaturalLess(v1, v2) } - if v1 == v2 { return sortorder.NaturalLess(id1, id2) } - if asc { - return less - } - return !less + + return less } diff --git a/internal/render/row_event.go b/internal/render/row_event.go index 1897426e..06015325 100644 --- a/internal/render/row_event.go +++ b/internal/render/row_event.go @@ -239,7 +239,12 @@ func (r RowEventSorter) Swap(i, j int) { func (r RowEventSorter) Less(i, j int) bool { f1, f2 := r.Events[i].Row.Fields, r.Events[j].Row.Fields id1, id2 := r.Events[i].Row.ID, r.Events[j].Row.ID - return Less(r.Asc, r.IsNumber, r.IsDuration, id1, id2, f1[r.Index], f2[r.Index]) + less := Less(r.IsNumber, r.IsDuration, id1, id2, f1[r.Index], f2[r.Index]) + if r.Asc { + return less + } + + return !less } // ---------------------------------------------------------------------------- diff --git a/internal/render/row_test.go b/internal/render/row_test.go index 43996421..09c1ae7c 100644 --- a/internal/render/row_test.go +++ b/internal/render/row_test.go @@ -317,6 +317,18 @@ func TestRowsSortDuration(t *testing.T) { asc bool e render.Rows }{ + "years": { + rows: render.Rows{ + {Fields: []string{testTime().Add(-365 * 24 * time.Hour).String(), "blee"}}, + {Fields: []string{testTime().String(), "duh"}}, + }, + col: 0, + asc: true, + e: render.Rows{ + {Fields: []string{testTime().String(), "duh"}}, + {Fields: []string{testTime().Add(-365 * 24 * time.Hour).String(), "blee"}}, + }, + }, "durationAsc": { rows: render.Rows{ {Fields: []string{testTime().Add(10 * time.Second).String(), "duh"}}, @@ -392,3 +404,36 @@ func TestRowsSortMetrics(t *testing.T) { }) } } + +func TestLess(t *testing.T) { + uu := map[string]struct { + isNumber, isDuration bool + id1, id2 string + v1, v2 string + e bool + }{ + "years": { + isNumber: false, + isDuration: true, + id1: "id1", + id2: "id2", + v1: "2y263d", + v2: "1y179d", + }, + "hours": { + isNumber: false, + isDuration: true, + id1: "id1", + id2: "id2", + v1: "2y263d", + v2: "19h", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, render.Less(u.isNumber, u.isDuration, u.id1, u.id2, u.v1, u.v2)) + }) + } +} diff --git a/internal/ui/table.go b/internal/ui/table.go index e967a217..ded01918 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -244,7 +244,7 @@ func (t *Table) doUpdate(data render.TableData) { custData.Namespace, colIndex, custData.Header.IsTimeCol(colIndex), - data.Header.IsMetricsCol(colIndex), + custData.Header.IsMetricsCol(colIndex), t.sortCol.asc, ) diff --git a/internal/view/exec.go b/internal/view/exec.go index 8300b774..e7bc2489 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -184,7 +184,7 @@ func clearScreen() { const ( k9sShell = "k9s-shell" k9sShellRetryCount = 10 - k9sShellRetryDelay = 1 * time.Second + k9sShellRetryDelay = 10 * time.Second ) func ssh(a *App, node string) error { diff --git a/internal/view/log_test.go b/internal/view/log_test.go index 583e9aad..7e966b80 100644 --- a/internal/view/log_test.go +++ b/internal/view/log_test.go @@ -108,10 +108,8 @@ func TestLogViewSave(t *testing.T) { dir := filepath.Join(app.Config.K9s.GetScreenDumpDir(), app.Config.K9s.CurrentCluster) c1, _ := os.ReadDir(dir) - fmt.Println("C1", c1) v.SaveCmd(nil) c2, _ := os.ReadDir(dir) - fmt.Println("C2", c2) assert.Equal(t, len(c2), len(c1)+1) } diff --git a/internal/view/ns.go b/internal/view/ns.go index f1d85435..f547484e 100644 --- a/internal/view/ns.go +++ b/internal/view/ns.go @@ -1,8 +1,6 @@ package view import ( - "time" - "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" @@ -85,7 +83,7 @@ func (n *Namespace) decorate(data render.TableData) render.TableData { Kind: render.EventUnchanged, Row: render.Row{ ID: client.NamespaceAll, - Fields: render.Fields{client.NamespaceAll, "Active", "", "", time.Now().String()}, + Fields: render.Fields{client.NamespaceAll, "Active", "", "", ""}, }, }, ) diff --git a/internal/view/table_int_test.go b/internal/view/table_int_test.go index 11bba71e..fc622a59 100644 --- a/internal/view/table_int_test.go +++ b/internal/view/table_int_test.go @@ -70,20 +70,49 @@ func TestTableViewFilter(t *testing.T) { v.CmdBuff().SetActive(true) v.CmdBuff().SetText("blee", "") - assert.Equal(t, 3, v.GetRowCount()) + assert.Equal(t, 5, v.GetRowCount()) } func TestTableViewSort(t *testing.T) { v := NewTable(client.NewGVR("test")) v.Init(makeContext()) v.SetModel(&mockTableModel{}) - v.SortColCmd("NAME", true)(nil) - assert.Equal(t, 3, v.GetRowCount()) - assert.Equal(t, "blee", v.GetCell(1, 0).Text) - v.SortInvertCmd(nil) - assert.Equal(t, 3, v.GetRowCount()) - assert.Equal(t, "fred", v.GetCell(1, 0).Text) + uu := map[string]struct { + sortCol string + sorted []string + reversed []string + }{ + "by_name": { + sortCol: "NAME", + sorted: []string{"r0", "r1", "r2", "r3"}, + reversed: []string{"r3", "r2", "r1", "r0"}, + }, + "by_age": { + sortCol: "AGE", + sorted: []string{"r0", "r1", "r2", "r3"}, + reversed: []string{"r3", "r2", "r1", "r0"}, + }, + "by_fred": { + sortCol: "FRED", + sorted: []string{"r3", "r2", "r0", "r1"}, + reversed: []string{"r1", "r0", "r2", "r3"}, + }, + } + + for k := range uu { + u := uu[k] + v.SortColCmd(u.sortCol, true)(nil) + assert.Equal(t, len(u.sorted)+1, v.GetRowCount()) + for i, s := range u.sorted { + assert.Equal(t, s, v.GetCell(i+1, 0).Text) + } + v.SortInvertCmd(nil) + assert.Equal(t, len(u.reversed)+1, v.GetRowCount()) + for i, s := range u.reversed { + assert.Equal(t, s, v.GetCell(i+1, 0).Text) + } + } } // ---------------------------------------------------------------------------- @@ -128,27 +157,35 @@ func (t *mockTableModel) SetRefreshRate(time.Duration) {} func makeTableData() render.TableData { t := render.NewTableData() - t.Header = render.Header{ render.HeaderColumn{Name: "NAMESPACE"}, render.HeaderColumn{Name: "NAME", Align: tview.AlignRight}, render.HeaderColumn{Name: "FRED"}, - render.HeaderColumn{Name: "AGE", Time: true, Decorator: render.AgeDecorator}, + render.HeaderColumn{Name: "AGE", Time: true}, } t.RowEvents = render.RowEvents{ render.RowEvent{ Row: render.Row{ - Fields: render.Fields{"ns1", "blee", "10", "3m"}, + Fields: render.Fields{"ns1", "r3", "10", "3y125d"}, }, }, render.RowEvent{ Row: render.Row{ - Fields: render.Fields{"ns1", "fred", "15", "1m"}, + Fields: render.Fields{"ns1", "r2", "15", "2y12d"}, }, Deltas: render.DeltaRow{"", "", "20", ""}, }, + render.RowEvent{ + Row: render.Row{ + Fields: render.Fields{"ns1", "r1", "20", "19h"}, + }, + }, + render.RowEvent{ + Row: render.Row{ + Fields: render.Fields{"ns1", "r0", "15", "10s"}, + }, + }, } - t.Namespace = "" return *t }