diff --git a/go.mod b/go.mod index e4d5956f..77061b84 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 7dd87699..b9d404e2 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= @@ -995,8 +995,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 168d1ac7..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: "3y196d", - }, } 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 }