K9s release v0.25.19 (#1610)

* release v0.25.19

* update deps revs
mine
Fernand Galiana 2022-06-27 19:20:22 -06:00 committed by GitHub
parent 69dc096717
commit bb5ba1306d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 713 additions and 677 deletions

View File

@ -13,7 +13,7 @@ RUN apk --no-cache add make git gcc libc-dev curl && make build
# Build the final Docker image
FROM alpine:3.14.3
ARG KUBECTL_VERSION="v1.21.2"
ARG KUBECTL_VERSION="v1.24.2"
COPY --from=build /k9s/execs/k9s /bin/k9s
RUN apk add --update ca-certificates \

View File

@ -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.18
VERSION ?= v0.25.19
IMG_NAME := derailed/k9s
IMAGE := ${IMG_NAME}:${VERSION}

View File

@ -0,0 +1,42 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
# Release v0.25.19
## 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 #1609](https://github.com/derailed/k9s/issues/1609) K9s fails to launch when active view does not exist
* [Issue #1593](https://github.com/derailed/k9s/issues/1593) Selection of namespace is changed automatically
* [Issue #1572](https://github.com/derailed/k9s/issues/1572) Wrong resource configuration being display after updating ingress
* [Issue #1569](https://github.com/derailed/k9s/issues/1569) Slight wording error when port forward already existS!
* [Issue #1565](https://github.com/derailed/k9s/issues/1565) Popeye stopped working
## Resolved PR
* [PR #1601](https://github.com/derailed/k9s/pull/1601) Ensure correct text in prompt when suspending cronjob
* [PR #1600](https://github.com/derailed/k9s/pull/1600) Fix typo in fastforwards annotation name
* [PR #1566](https://github.com/derailed/k9s/pull/1566) Correct typo in skins
* [PR #1555](https://github.com/derailed/k9s/pull/1555) Update benchmark command in readme
* [PR #1553](https://github.com/derailed/k9s/pull/1553) Allow `all` deletion propagation policy
* [PR #1539](https://github.com/derailed/k9s/pull/1539) Plugin to allow default chart values retrieval
* [PR #1529](https://github.com/derailed/k9s/pull/1529) Update example k9s config file
* [PR #1518](https://github.com/derailed/k9s/pull/1519) Add Helm values support
* [PR #1493](https://github.com/derailed/k9s/pull/1493) Fix padding is not 0 in fullscreen
* [PR #1422](https://github.com/derailed/k9s/pull/1422) Fix typo in README
---
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

142
go.mod
View File

@ -1,6 +1,6 @@
module github.com/derailed/k9s
go 1.17
go 1.18
replace (
github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d
@ -13,86 +13,90 @@ require (
github.com/atotto/clipboard v0.1.4
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/cenkalti/backoff/v4 v4.1.2
github.com/derailed/popeye v0.9.8
github.com/derailed/popeye v0.10.0
github.com/derailed/tview v0.6.6
github.com/fatih/color v1.13.0
github.com/fsnotify/fsnotify v1.5.1
github.com/fvbommel/sortorder v1.0.2
github.com/gdamore/tcell/v2 v2.4.0
github.com/ghodss/yaml v1.0.0
github.com/mattn/go-colorable v0.1.12
github.com/mattn/go-runewidth v0.0.13
github.com/petergtz/pegomock v2.9.0+incompatible
github.com/rakyll/hey v0.1.4
github.com/rs/zerolog v1.26.0
github.com/sahilm/fuzzy v0.1.0
github.com/spf13/cobra v1.2.1
github.com/stretchr/testify v1.7.0
github.com/spf13/cobra v1.4.0
github.com/stretchr/testify v1.7.1
golang.org/x/text v0.3.7
gopkg.in/yaml.v2 v2.4.0
helm.sh/helm/v3 v3.7.1
k8s.io/api v0.22.3
k8s.io/apimachinery v0.22.3
k8s.io/cli-runtime v0.22.3
k8s.io/client-go v0.22.3
k8s.io/klog/v2 v2.30.0
k8s.io/kubectl v0.22.3
k8s.io/metrics v0.22.3
helm.sh/helm/v3 v3.9.0
k8s.io/api v0.24.2
k8s.io/apimachinery v0.24.2
k8s.io/cli-runtime v0.24.2
k8s.io/client-go v0.24.2
k8s.io/klog/v2 v2.60.1
k8s.io/kubectl v0.24.2
k8s.io/metrics v0.24.2
sigs.k8s.io/yaml v1.3.0
)
require (
cloud.google.com/go v0.81.0 // indirect
cloud.google.com/go v0.99.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
github.com/Azure/go-autorest/autorest v0.11.20 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/BurntSushi/toml v1.0.0 // indirect
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
github.com/Masterminds/squirrel v1.5.0 // indirect
github.com/Microsoft/go-winio v0.4.17 // indirect
github.com/Microsoft/hcsshim v0.8.21 // indirect
github.com/Masterminds/squirrel v1.5.2 // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/Microsoft/hcsshim v0.9.2 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
github.com/aws/aws-sdk-go v1.35.21 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/containerd/containerd v1.5.7 // indirect
github.com/containerd/continuity v0.1.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.2 // 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/continuity v0.2.2 // 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.7+incompatible // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.3 // indirect
github.com/docker/cli v20.10.11+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.14+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/evanphx/json-patch v4.11.0+incompatible // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-logr/logr v1.2.0 // indirect
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
github.com/go-logr/logr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.0.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
@ -100,74 +104,74 @@ require (
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmoiron/sqlx v1.3.1 // indirect
github.com/jmoiron/sqlx v1.3.4 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/klauspost/compress v1.11.13 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.10.0 // indirect
github.com/lib/pq v1.10.4 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mitchellh/copystructure v1.1.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/onsi/gomega v1.10.3 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/gomega v1.15.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v1.0.2 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/opencontainers/runc v1.1.1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect
github.com/rubenv/sql-migrate v1.1.1 // indirect
github.com/russross/blackfriday v1.5.2 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
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-20211025201205-69cdffdb9359 // indirect
golang.org/x/term v0.0.0-20210406210042-72f3dc4e9b72 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // 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
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
google.golang.org/grpc v1.38.0 // indirect
google.golang.org/protobuf v1.26.0 // indirect
gopkg.in/gorp.v1 v1.7.2 // indirect
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
google.golang.org/grpc v1.43.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/apiextensions-apiserver v0.22.1 // indirect
k8s.io/apiserver v0.22.1 // indirect
k8s.io/component-base v0.22.3 // indirect
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect
oras.land/oras-go v0.4.0 // indirect
sigs.k8s.io/kustomize/api v0.8.11 // indirect
sigs.k8s.io/kustomize/kyaml v0.11.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
k8s.io/apiextensions-apiserver v0.24.0 // indirect
k8s.io/apiserver v0.24.0 // indirect
k8s.io/component-base v0.24.2 // indirect
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
oras.land/oras-go v1.1.1 // indirect
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
sigs.k8s.io/kustomize/api v0.11.4 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
)

658
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -166,7 +166,6 @@ func (k *K9s) ActiveCluster() *Cluster {
func (k *K9s) GetScreenDumpDir() string {
screenDumpDir := k.ScreenDumpDir
if k.manualScreenDumpDir != nil && *k.manualScreenDumpDir != "" {
screenDumpDir = *k.manualScreenDumpDir
}

View File

@ -2,10 +2,8 @@ package dao
import (
"context"
"fmt"
"github.com/derailed/k9s/internal"
"github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
@ -28,21 +26,17 @@ func (h *HorizontalPodAutoscaler) List(ctx context.Context, ns string) ([]runtim
lsel = sel.AsSelector()
}
gvrs := []string{
"autoscaling/v2beta2/horizontalpodautoscalers",
"autoscaling/v2beta1/horizontalpodautoscalers",
"autoscaling/v1/horizontalpodautoscalers",
rev, err := h.Factory.Client().ServerVersion()
if err != nil {
return nil, err
}
for _, gvr := range gvrs {
oo, err := h.list(gvr, ns, lsel)
if err == nil && len(oo) > 0 {
return oo, nil
}
gvr := "autoscaling/v1/horizontalpodautoscalers"
if rev.Minor >= "23" {
gvr = "autoscaling/v2/horizontalpodautoscalers"
}
log.Error().Err(fmt.Errorf("No results for any known HPA versions"))
return []runtime.Object{}, nil
return h.list(gvr, ns, lsel)
}
func (h *HorizontalPodAutoscaler) list(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) {

View File

@ -109,14 +109,19 @@ func (n *Node) Drain(path string, opts DrainOptions, w io.Writer) error {
// Get returns a node resource.
func (n *Node) Get(ctx context.Context, path string) (runtime.Object, error) {
o, err := n.Resource.Get(ctx, path)
oo, err := n.Resource.List(ctx, "")
if err != nil {
return o, err
return nil, err
}
u, ok := o.(*unstructured.Unstructured)
if !ok {
return nil, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o)
var raw *unstructured.Unstructured
for _, o := range oo {
if u, ok := o.(*unstructured.Unstructured); ok && u.GetName() == path {
raw = u
}
}
if raw == nil {
return nil, fmt.Errorf("unable to locate node %s", path)
}
var nmx *mv1beta1.NodeMetrics
@ -124,7 +129,7 @@ func (n *Node) Get(ctx context.Context, path string) (runtime.Object, error) {
nmx, _ = client.DialMetrics(n.Client()).FetchNodeMetrics(ctx, path)
}
return &render.NodeWithMetrics{Raw: u, MX: nmx}, nil
return &render.NodeWithMetrics{Raw: raw, MX: nmx}, nil
}
// List returns a collection of node resources.

26
internal/dao/ns.go Normal file
View File

@ -0,0 +1,26 @@
package dao
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
)
var (
_ Accessor = (*Pod)(nil)
)
// Namespace represents a namespace resource.
type Namespace struct {
Generic
}
// List returns a collection of nodes.
func (n *Namespace) List(ctx context.Context, ns string) ([]runtime.Object, error) {
oo, err := n.Generic.List(ctx, ns)
if err != nil {
return nil, err
}
return oo, nil
}

View File

@ -92,6 +92,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
client.NewGVR("batch/v1/cronjobs"): &CronJob{},
client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{},
client.NewGVR("batch/v1/jobs"): &Job{},
client.NewGVR("v1/namespaces"): &Namespace{},
// BOZO!! Revamp with latest...
// client.NewGVR("openfaas"): &OpenFaas{},
client.NewGVR("popeye"): &Popeye{},

View File

@ -72,10 +72,10 @@ func (g *Generic) Render(o interface{}, ns string, r *Row) error {
r.ID = client.FQN(nns, name)
r.Fields = make(Fields, 0, len(g.Header(ns)))
r.Fields = append(r.Fields, nns)
var ageCell interface{}
var duration interface{}
for i, c := range row.Cells {
if g.ageIndex > 0 && i == g.ageIndex {
ageCell = c
duration = c
continue
}
if c == nil {
@ -84,8 +84,8 @@ func (g *Generic) Render(o interface{}, ns string, r *Row) error {
}
r.Fields = append(r.Fields, fmt.Sprintf("%v", c))
}
if ageCell != nil {
r.Fields = append(r.Fields, fmt.Sprintf("%v", ageCell))
if d, ok := duration.(string); ok {
r.Fields = append(r.Fields, d)
}
return nil

View File

@ -70,7 +70,7 @@ func TestGenericRender(t *testing.T) {
ns: client.ClusterScope,
table: makeAgeGeneric(),
eID: "-/fred",
eFields: render.Fields{"-", "c1", "c2", "Age"},
eFields: render.Fields{"-", "c1", "c2", "2d"},
eHeader: render.Header{
render.HeaderColumn{Name: "NAMESPACE"},
render.HeaderColumn{Name: "A"},
@ -172,7 +172,7 @@ func makeAgeGeneric() *metav1beta1.Table {
},
Cells: []interface{}{
"c1",
"Age",
"2d",
"c2",
},
},

View File

@ -16,25 +16,25 @@ import (
"k8s.io/apimachinery/pkg/util/duration"
)
func runesToNum(rr []rune) int {
var n int
m := 1
func runesToNum(rr []rune) int64 {
var r int64
var m int64 = 1
for i := len(rr) - 1; i >= 0; i-- {
v := int(rr[i] - '0')
n += v * m
v := int64(rr[i] - '0')
r += v * m
m *= 10
}
return n
return r
}
func durationToSeconds(duration string) string {
func durationToSeconds(duration string) int64 {
if len(duration) == 0 {
return duration
return 0
}
num := make([]rune, 0, 5)
var n, m int
var n, m int64
for _, r := range duration {
switch r {
case 'y':
@ -54,7 +54,7 @@ func durationToSeconds(duration string) string {
n, num = n+runesToNum(num)*m, num[:0]
}
return strconv.Itoa(n)
return n
}
// AsThousands prints a number with thousand separator.
@ -194,17 +194,25 @@ func boolToStr(b bool) string {
}
}
func toAge(timestamp metav1.Time) string {
return time.Since(timestamp.Time).String()
func toAge(t metav1.Time) string {
if t.IsZero() {
return UnknownValue
}
return duration.HumanDuration(time.Since(t.Time))
}
func toAgeHuman(s string) string {
d, err := time.ParseDuration(s)
if len(s) == 0 {
return UnknownValue
}
t, err := time.Parse(time.RFC3339, s)
if err != nil {
return NAValue
}
return duration.HumanDuration(d)
return duration.HumanDuration(time.Since(t))
}
// Truncate a string to the given l and suffix ellipsis if needed.

View File

@ -1,6 +1,7 @@
package render
import (
"fmt"
"testing"
"time"
@ -54,17 +55,18 @@ func TestLabelize(t *testing.T) {
func TestDurationToSecond(t *testing.T) {
uu := map[string]struct {
s, e string
s string
e int64
}{
"seconds": {s: "22s", e: "22"},
"minutes": {s: "22m", e: "1320"},
"hours": {s: "12h", e: "43200"},
"days": {s: "3d", e: "259200"},
"day_hour": {s: "3d9h", e: "291600"},
"day_hour_minute": {s: "2d22h3m", e: "252180"},
"day_hour_minute_seconds": {s: "2d22h3m50s", e: "252230"},
"year": {s: "3y", e: "94608000"},
"year_day": {s: "1y2d", e: "31708800"},
"seconds": {s: "22s", e: 22},
"minutes": {s: "22m", e: 1320},
"hours": {s: "12h", e: 43200},
"days": {s: "3d", e: 259200},
"day_hour": {s: "3d9h", e: 291600},
"day_hour_minute": {s: "2d22h3m", e: 252180},
"day_hour_minute_seconds": {s: "2d22h3m50s", e: 252230},
"year": {s: "3y", e: 94608000},
"year_day": {s: "1y2d", e: 31708800},
}
for k := range uu {
@ -90,36 +92,42 @@ func TestToAge(t *testing.T) {
t time.Time
e string
}{
"zero": {
t: time.Time{},
e: UnknownValue,
},
"good": {
t: time.Now().Add(-10 * time.Second),
e: "10",
t: testTime().Add(-10 * time.Second),
e: "3y196d",
},
}
for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, uc.e, toAge(metav1.Time{Time: uc.t})[:2])
assert.Equal(t, uc.e, toAge(metav1.Time{Time: uc.t}))
})
}
}
func TestToAgeHuma(t *testing.T) {
func TestToAgeHuman(t *testing.T) {
uu := map[string]struct {
t time.Time
e string
t, e string
}{
"blank": {
t: "",
e: UnknownValue,
},
"good": {
t: time.Now().Add(-10 * time.Second),
e: "10",
t: time.Now().Add(-10 * time.Second).Format(time.RFC3339Nano),
e: "10s",
},
}
for k := range uu {
uc := uu[k]
u := uu[k]
t.Run(k, func(t *testing.T) {
ti := toAge(metav1.Time{Time: uc.t})
assert.Equal(t, uc.e, toAgeHuman(ti)[:2])
assert.Equal(t, u.e, toAgeHuman(u.t))
})
}
}
@ -376,7 +384,7 @@ func BenchmarkMapToStr(b *testing.B) {
func TestRunesToNum(t *testing.T) {
uu := map[string]struct {
rr []rune
e int
e int64
}{
"0": {
rr: []rune(""),
@ -466,3 +474,13 @@ func BenchmarkIntToStr(b *testing.B) {
IntToStr(v)
}
}
// Helpers
func testTime() time.Time {
t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00")
if err != nil {
fmt.Println("TestTime Failed", err)
}
return t
}

View File

@ -8,8 +8,7 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/tview"
autoscalingv1 "k8s.io/api/autoscaling/v1"
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
autoscalingv2 "k8s.io/api/autoscaling/v2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
@ -46,10 +45,8 @@ func (h HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error
switch v {
case "autoscaling/v1":
return h.renderV1(raw, ns, r)
case "autoscaling/v2beta1":
return h.renderV2b1(raw, ns, r)
case "autoscaling/v2beta2":
return h.renderV2b2(raw, ns, r)
case "autoscaling/v2":
return h.renderV2(raw, ns, r)
default:
return fmt.Errorf("Unhandled HPA version %q", v)
}
@ -78,8 +75,8 @@ func (h HorizontalPodAutoscaler) renderV1(raw *unstructured.Unstructured, _ stri
return nil
}
func (h HorizontalPodAutoscaler) renderV2b1(raw *unstructured.Unstructured, _ string, r *Row) error {
var hpa autoscalingv2beta1.HorizontalPodAutoscaler
func (h HorizontalPodAutoscaler) renderV2(raw *unstructured.Unstructured, _ string, r *Row) error {
var hpa autoscalingv2.HorizontalPodAutoscaler
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &hpa)
if err != nil {
return err
@ -90,30 +87,7 @@ func (h HorizontalPodAutoscaler) renderV2b1(raw *unstructured.Unstructured, _ st
hpa.Namespace,
hpa.ObjectMeta.Name,
hpa.Spec.ScaleTargetRef.Name,
toMetricsV2b1(hpa.Spec.Metrics, hpa.Status.CurrentMetrics),
strconv.Itoa(int(*hpa.Spec.MinReplicas)),
strconv.Itoa(int(hpa.Spec.MaxReplicas)),
strconv.Itoa(int(hpa.Status.CurrentReplicas)),
"",
toAge(hpa.ObjectMeta.CreationTimestamp),
}
return nil
}
func (h HorizontalPodAutoscaler) renderV2b2(raw *unstructured.Unstructured, _ string, r *Row) error {
var hpa autoscalingv2beta2.HorizontalPodAutoscaler
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &hpa)
if err != nil {
return err
}
r.ID = client.MetaFQN(hpa.ObjectMeta)
r.Fields = Fields{
hpa.Namespace,
hpa.ObjectMeta.Name,
hpa.Spec.ScaleTargetRef.Name,
toMetricsV2b2(hpa.Spec.Metrics, hpa.Status.CurrentMetrics),
toMetricsV2(hpa.Spec.Metrics, hpa.Status.CurrentMetrics),
strconv.Itoa(int(*hpa.Spec.MinReplicas)),
strconv.Itoa(int(hpa.Spec.MaxReplicas)),
strconv.Itoa(int(hpa.Status.CurrentReplicas)),
@ -140,30 +114,7 @@ func toMetricsV1(spec autoscalingv1.HorizontalPodAutoscalerSpec, status autoscal
return current + "/" + target + "%"
}
func toMetricsV2b1(specs []autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
if len(specs) == 0 {
return MissingValue
}
list := make([]string, 0, len(specs))
for i, spec := range specs {
list = append(list, checkHPAType(i, spec, statuses))
}
max, more := 10, false
if len(list) > max {
list, more = list[:max], true
}
ret := strings.Join(list, ", ")
if more {
return ret + " + " + strconv.Itoa(len(list)-max) + "more..."
}
return ret
}
func toMetricsV2b2(specs []autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string {
func toMetricsV2(specs []autoscalingv2.MetricSpec, statuses []autoscalingv2.MetricStatus) string {
if len(specs) == 0 {
return MissingValue
}
@ -174,20 +125,20 @@ func toMetricsV2b2(specs []autoscalingv2beta2.MetricSpec, statuses []autoscaling
// nolint:exhaustive
switch spec.Type {
case autoscalingv2beta2.ExternalMetricSourceType:
list = append(list, externalMetricsV2b2(i, spec, statuses))
case autoscalingv2beta2.PodsMetricSourceType:
case autoscalingv2.ExternalMetricSourceType:
list = append(list, externalMetricsV2(i, spec, statuses))
case autoscalingv2.PodsMetricSourceType:
if len(statuses) > i && statuses[i].Pods != nil {
current = statuses[i].Pods.Current.AverageValue.String()
}
list = append(list, current+"/"+spec.Pods.Target.AverageValue.String())
case autoscalingv2beta2.ObjectMetricSourceType:
case autoscalingv2.ObjectMetricSourceType:
if len(statuses) > i && statuses[i].Object != nil {
current = statuses[i].Object.Current.Value.String()
}
list = append(list, current+"/"+spec.Object.Target.Value.String())
case autoscalingv2beta2.ResourceMetricSourceType:
list = append(list, resourceMetricsV2b2(i, spec, statuses))
case autoscalingv2.ResourceMetricSourceType:
list = append(list, resourceMetricsV2(i, spec, statuses))
default:
list = append(list, "<unknown type>")
}
@ -206,31 +157,31 @@ func toMetricsV2b2(specs []autoscalingv2beta2.MetricSpec, statuses []autoscaling
return ret
}
func checkHPAType(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
func checkHPAType(i int, spec autoscalingv2.MetricSpec, statuses []autoscalingv2.MetricStatus) string {
current := "<unknown>"
// nolint:exhaustive
switch spec.Type {
case autoscalingv2beta1.ExternalMetricSourceType:
return externalMetricsV2b1(i, spec, statuses)
case autoscalingv2beta1.PodsMetricSourceType:
case autoscalingv2.ExternalMetricSourceType:
return externalMetricsV2(i, spec, statuses)
case autoscalingv2.PodsMetricSourceType:
if len(statuses) > i && statuses[i].Pods != nil {
current = statuses[i].Pods.CurrentAverageValue.String()
current = statuses[i].Pods.Current.AverageValue.String()
}
return current + "/" + spec.Pods.TargetAverageValue.String()
case autoscalingv2beta1.ObjectMetricSourceType:
return current + "/" + spec.Pods.Target.AverageValue.String()
case autoscalingv2.ObjectMetricSourceType:
if len(statuses) > i && statuses[i].Object != nil {
current = statuses[i].Object.CurrentValue.String()
current = statuses[i].Object.Current.Value.String()
}
return current + "/" + spec.Object.TargetValue.String()
case autoscalingv2beta1.ResourceMetricSourceType:
return resourceMetricsV2b1(i, spec, statuses)
return current + "/" + spec.Object.Target.Value.String()
case autoscalingv2.ResourceMetricSourceType:
return resourceMetricsV2(i, spec, statuses)
}
return "<unknown type>"
}
func externalMetricsV2b2(i int, spec autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string {
func externalMetricsV2(i int, spec autoscalingv2.MetricSpec, statuses []autoscalingv2.MetricStatus) string {
current := "<unknown>"
if spec.External.Target.AverageValue != nil {
@ -246,7 +197,7 @@ func externalMetricsV2b2(i int, spec autoscalingv2beta2.MetricSpec, statuses []a
return current + "/" + spec.External.Target.Value.String()
}
func resourceMetricsV2b2(i int, spec autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string {
func resourceMetricsV2(i int, spec autoscalingv2.MetricSpec, statuses []autoscalingv2.MetricStatus) string {
current := "<unknown>"
if spec.Resource.Target.AverageValue != nil {
@ -268,48 +219,48 @@ func resourceMetricsV2b2(i int, spec autoscalingv2beta2.MetricSpec, statuses []a
return current + "/" + target
}
func externalMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
current := "<unknown>"
if spec.External.TargetAverageValue != nil {
if len(statuses) > i && statuses[i].External != nil && statuses[i].External.CurrentAverageValue != nil {
current = statuses[i].External.CurrentAverageValue.String()
}
return current + "/" + spec.External.TargetAverageValue.String() + " (avg)"
}
if len(statuses) > i && statuses[i].External != nil {
current = statuses[i].External.CurrentValue.String()
}
// func externalMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
// current := "<unknown>"
// if spec.External.TargetAverageValue != nil {
// if len(statuses) > i && statuses[i].External != nil && statuses[i].External.CurrentAverageValue != nil {
// current = statuses[i].External.CurrentAverageValue.String()
// }
// return current + "/" + spec.External.TargetAverageValue.String() + " (avg)"
// }
// if len(statuses) > i && statuses[i].External != nil {
// current = statuses[i].External.CurrentValue.String()
// }
return current + "/" + spec.External.TargetValue.String()
}
// return current + "/" + spec.External.TargetValue.String()
// }
func resourceMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
current := "<unknown>"
// func resourceMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
// current := "<unknown>"
if status := checkTargetMetricsV2b1(i, spec, statuses); status != "" {
return status
}
// if status := checkTargetMetricsV2b1(i, spec, statuses); status != "" {
// return status
// }
if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.CurrentAverageUtilization != nil {
current = IntToStr(int(*statuses[i].Resource.CurrentAverageUtilization))
}
// if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.CurrentAverageUtilization != nil {
// current = IntToStr(int(*statuses[i].Resource.CurrentAverageUtilization))
// }
target := "<auto>"
if spec.Resource.TargetAverageUtilization != nil {
target = IntToStr(int(*spec.Resource.TargetAverageUtilization))
}
// target := "<auto>"
// if spec.Resource.TargetAverageUtilization != nil {
// target = IntToStr(int(*spec.Resource.TargetAverageUtilization))
// }
return current + "/" + target
}
// return current + "/" + target
// }
func checkTargetMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
if spec.Resource.TargetAverageValue == nil {
return ""
}
// func checkTargetMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
// if spec.Resource.TargetAverageValue == nil {
// return ""
// }
var current string
if len(statuses) > i && statuses[i].Resource != nil {
current = statuses[i].Resource.CurrentAverageValue.String()
}
return current + "/" + spec.Resource.TargetAverageValue.String()
}
// var current string
// if len(statuses) > i && statuses[i].Resource != nil {
// current = statuses[i].Resource.CurrentAverageValue.String()
// }
// return current + "/" + spec.Resource.TargetAverageValue.String()
// }

View File

@ -80,7 +80,7 @@ func (Pod) Header(ns string) Header {
HeaderColumn{Name: "VALID", Wide: true},
HeaderColumn{Name: "NOMINATED NODE", Wide: true},
HeaderColumn{Name: "READINESS GATES", Wide: true},
HeaderColumn{Name: "AGE", Time: true, Decorator: AgeDecorator},
HeaderColumn{Name: "AGE", Time: true},
}
}

View File

@ -3,9 +3,7 @@ package render
import (
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/fvbommel/sortorder"
)
@ -177,32 +175,33 @@ func (s RowSorter) Swap(i, j int) {
}
func (s RowSorter) Less(i, j int) bool {
return Less(s.Asc, s.IsNumber, s.IsDuration, s.Rows[i].Fields[s.Index], s.Rows[j].Fields[s.Index])
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)
}
// ----------------------------------------------------------------------------
// Helpers...
func toAgeDuration(dur string) string {
d, err := time.ParseDuration(dur)
if err != nil {
return durationToSeconds(dur)
}
return strconv.Itoa(int(d.Seconds()))
}
// Less return true if c1 < c2.
func Less(asc, isNumber, isDuration bool, c1, c2 string) bool {
if isNumber {
c1, c2 = strings.Replace(c1, ",", "", -1), strings.Replace(c2, ",", "", -1)
func Less(asc, isNumber, isDuration bool, id1, id2, v1, v2 string) bool {
var less bool
switch {
case isNumber:
v1, v2 = strings.Replace(v1, ",", "", -1), strings.Replace(v2, ",", "", -1)
less = sortorder.NaturalLess(v1, v2)
case isDuration:
d1, d2 := durationToSeconds(v1), durationToSeconds(v2)
less = d1 <= d2
default:
less = sortorder.NaturalLess(v1, v2)
}
if isDuration {
c1, c2 = toAgeDuration(c1), toAgeDuration(c2)
if v1 == v2 {
return sortorder.NaturalLess(id1, id2)
}
b := sortorder.NaturalLess(c1, c2)
if asc {
return b
return less
}
return !b
return !less
}

View File

@ -195,6 +195,10 @@ func (r RowEvents) FindIndex(id string) (int, bool) {
return 0, false
}
type tuple struct {
field, id string
}
// Sort rows based on column index and order.
func (r RowEvents) Sort(ns string, sortCol int, isDuration, numCol, asc bool) {
if sortCol == -1 {
@ -210,24 +214,6 @@ func (r RowEvents) Sort(ns string, sortCol int, isDuration, numCol, asc bool) {
IsDuration: isDuration,
}
sort.Sort(t)
iids, fields := map[string][]string{}, make(StringSet, 0, len(r))
for _, re := range r {
field := re.Row.Fields[sortCol]
if isDuration {
field = toAgeDuration(field)
}
fields = fields.Add(field)
iids[field] = append(iids[field], re.Row.ID)
}
ids := make([]string, 0, len(r))
for _, field := range fields {
sort.StringSlice(iids[field]).Sort()
ids = append(ids, iids[field]...)
}
s := IdSorter{Ids: ids, Events: r}
sort.Sort(s)
}
// ----------------------------------------------------------------------------
@ -252,7 +238,8 @@ 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
return Less(r.Asc, r.IsNumber, r.IsDuration, f1[r.Index], f2[r.Index])
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])
}
// ----------------------------------------------------------------------------

View File

@ -2,6 +2,7 @@ package render_test
import (
"testing"
"time"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
@ -409,39 +410,24 @@ func TestRowEventsDelete(t *testing.T) {
func TestRowEventsSort(t *testing.T) {
uu := map[string]struct {
re render.RowEvents
col int
age, num, asc bool
e render.RowEvents
re render.RowEvents
col int
duration, num, asc bool
e render.RowEvents
}{
"age_time": {
re: render.RowEvents{
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "1h10m10.5s"}}},
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "10.5s"}}},
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3h20m5.2s"}}},
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", testTime().Add(20 * time.Second).String()}}},
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", testTime().Add(10 * time.Second).String()}}},
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", testTime().String()}}},
},
col: 2,
asc: true,
age: true,
col: 2,
asc: true,
duration: true,
e: render.RowEvents{
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "10.5s"}}},
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "1h10m10.5s"}}},
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3h20m5.2s"}}},
},
},
"age_duration": {
re: render.RowEvents{
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "32d"}}},
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "1m10s"}}},
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3h20m5s"}}},
},
col: 2,
asc: true,
age: true,
e: render.RowEvents{
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "1m10s"}}},
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3h20m5s"}}},
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "32d"}}},
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", testTime().String()}}},
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", testTime().Add(10 * time.Second).String()}}},
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", testTime().Add(20 * time.Second).String()}}},
},
},
"col0": {
@ -483,7 +469,7 @@ func TestRowEventsSort(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
u.re.Sort("", u.col, u.age, u.num, u.asc)
u.re.Sort("", u.col, u.duration, u.num, u.asc)
assert.Equal(t, u.e, u.re)
})
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"reflect"
"testing"
"time"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
@ -318,25 +319,25 @@ func TestRowsSortDuration(t *testing.T) {
}{
"durationAsc": {
rows: render.Rows{
{Fields: []string{"10m10s", "duh"}},
{Fields: []string{"19s", "blee"}},
{Fields: []string{testTime().Add(10 * time.Second).String(), "duh"}},
{Fields: []string{testTime().String(), "blee"}},
},
col: 0,
asc: true,
e: render.Rows{
{Fields: []string{"19s", "blee"}},
{Fields: []string{"10m10s", "duh"}},
{Fields: []string{testTime().String(), "blee"}},
{Fields: []string{testTime().Add(10 * time.Second).String(), "duh"}},
},
},
"durationDesc": {
rows: render.Rows{
{Fields: []string{"10m10s", "duh"}},
{Fields: []string{"19s", "blee"}},
{Fields: []string{testTime().Add(10 * time.Second).String(), "duh"}},
{Fields: []string{testTime().String(), "blee"}},
},
col: 0,
e: render.Rows{
{Fields: []string{"10m10s", "duh"}},
{Fields: []string{"19s", "blee"}},
{Fields: []string{testTime().Add(10 * time.Second).String(), "duh"}},
{Fields: []string{testTime().String(), "blee"}},
},
},
}

View File

@ -26,9 +26,6 @@ func ComputeMaxColumns(pads MaxyPad, sortColName string, header render.Header, e
var row int
for _, e := range ee {
for index, field := range e.Row.Fields {
if header.IsTimeCol(index) {
field = toAgeHuman(field)
}
width := len(field) + colPadding
if index < len(pads) && width > pads[index] {
pads[index] = width
@ -60,10 +57,10 @@ func Pad(s string, width int) string {
}
func toAgeHuman(s string) string {
d, err := time.ParseDuration(s)
t, err := time.Parse(time.RFC3339Nano, s)
if err != nil {
return render.NAValue
}
return duration.HumanDuration(d)
return duration.HumanDuration(time.Since(t))
}

View File

@ -103,8 +103,8 @@ func (s *SelectTable) SelectRow(r int, broadcast bool) {
if !broadcast {
s.SetSelectionChangedFunc(nil)
}
if s.model.Count() > 0 && r >= s.model.Count() {
r = s.model.Count()
if c := s.model.Count(); c > 0 && r-1 > c {
r = c + 1
}
defer s.SetSelectionChangedFunc(s.selectionChanged)
s.Select(r, 0)

View File

@ -406,6 +406,10 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
if client.IsAllNamespace(ns) {
b.GetTable().SetSortCol("NAMESPACE", true)
}
if err := b.app.switchNS(ns); err != nil {
b.App().Flash().Err(err)
return nil

View File

@ -163,8 +163,7 @@ func (c *Command) defaultCmd() error {
if err := c.run(cmd, "", true); err != nil {
log.Error().Err(err).Msgf("Default run command failed %q", cmd)
c.app.cowCmd(err.Error())
return err
return c.run("pod", "", true)
}
return nil
}

View File

@ -174,7 +174,7 @@ func (c *Container) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey {
}
if _, ok := c.App().factory.ForwarderFor(fwFQN(c.GetTable().Path, path)); ok {
c.App().Flash().Err(fmt.Errorf("A port-forward already exist on container %s", c.GetTable().Path))
c.App().Flash().Err(fmt.Errorf("A port-forward already exists on container %s", c.GetTable().Path))
return nil
}

View File

@ -6,6 +6,8 @@ import (
"sort"
"strconv"
"strings"
"github.com/rs/zerolog/log"
)
// Env represent K9s and K8s available environment variables.
@ -34,7 +36,8 @@ func (e Env) Substitute(arg string) (string, error) {
}
v, ok := e[strings.ToUpper(key)]
if !ok {
return "", fmt.Errorf("no environment matching key %q:%q", k, key)
log.Warn().Msgf("no k9s environment matching key %q:%q", k, key)
continue
}
if b, err := strconv.ParseBool(v); err == nil {
if inverse {

View File

@ -1,7 +1,6 @@
package view
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
@ -17,7 +16,7 @@ func TestEnvReplace(t *testing.T) {
"simple": {arg: "$A", e: "10"},
"substring": {arg: "$A and $AA", e: "10 and 20"},
"with-text": {arg: "Something $A", e: "Something 10"},
"noMatch": {arg: "blah blah and $BLEE", err: errors.New(`no environment matching key "$BLEE":"BLEE"`), e: ""},
"noMatch": {arg: "blah blah and $BLEE", e: "blah blah and $BLEE"},
"lower": {arg: "And then $b happened", e: "And then blee happened"},
"dash": {arg: "$col0", e: "fred"},
"mix": {arg: "$col0 and then $a but $B", e: "fred and then 10 but blee"},

View File

@ -21,7 +21,7 @@ func TestHelp(t *testing.T) {
v := view.NewHelp(app)
assert.Nil(t, v.Init(ctx))
assert.Equal(t, 25, v.GetRowCount())
assert.Equal(t, 26, v.GetRowCount())
assert.Equal(t, 6, v.GetColumnCount())
assert.Equal(t, "<a>", strings.TrimSpace(v.GetCell(1, 0).Text))
assert.Equal(t, "Attach", strings.TrimSpace(v.GetCell(1, 1).Text))

View File

@ -350,7 +350,6 @@ func (l *Log) Flush(lines [][]byte) {
if l.cancelUpdates {
break
}
log.Debug().Msgf("FLUSH %q", string(lines[i]))
_, _ = l.ansiWriter.Write(lines[i])
}
if l.follow {

View File

@ -57,7 +57,7 @@ func (p *PortForwardExtender) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
if p.App().factory.Forwarders().IsPodForwarded(path) {
p.App().Flash().Errf("A PortForward already exist for pod %s", pod.Name)
p.App().Flash().Errf("A PortForward already exists for pod %s", pod.Name)
return nil
}
if err := showFwdDialog(p, podName, startFwdCB); err != nil {

View File

@ -75,6 +75,7 @@ func (p *Pod) bindKeys(aa ui.KeyActions) {
}
aa.Add(ui.KeyActions{
ui.KeyN: ui.NewKeyAction("Show Node", p.showNode, true),
ui.KeyF: ui.NewKeyAction("Show PortForward", p.showPFCmd, true),
ui.KeyShiftR: ui.NewKeyAction("Sort Ready", p.GetTable().SortColCmd(readyCol, true), false),
ui.KeyShiftT: ui.NewKeyAction("Sort Restart", p.GetTable().SortColCmd("RESTARTS", false), false),
@ -130,6 +131,37 @@ func (p *Pod) coContext(ctx context.Context) context.Context {
// Handlers...
func (p *Pod) showNode(evt *tcell.EventKey) *tcell.EventKey {
path := p.GetTable().GetSelectedItem()
if path == "" {
return evt
}
pod, err := fetchPod(p.App().factory, path)
if err != nil {
p.App().Flash().Err(err)
return nil
}
if pod.Spec.NodeName == "" {
p.App().Flash().Err(errors.New("no node assigned"))
return nil
}
no := NewNode(client.NewGVR("v1/nodes"))
no.SetInstance(pod.Spec.NodeName)
//no.SetContextFn(nodeContext(pod.Spec.NodeName))
if err := p.App().inject(no); err != nil {
p.App().Flash().Err(err)
}
return nil
}
func nodeContext(path string) ContextFunc {
return func(ctx context.Context) context.Context {
ctx = context.WithValue(ctx, internal.KeyPath, path)
return ctx
}
}
func (p *Pod) showPFCmd(evt *tcell.EventKey) *tcell.EventKey {
path := p.GetTable().GetSelectedItem()
if path == "" {

View File

@ -16,7 +16,7 @@ func TestPodNew(t *testing.T) {
assert.Nil(t, po.Init(makeCtx()))
assert.Equal(t, "Pods", po.Name())
assert.Equal(t, 24, len(po.Hints()))
assert.Equal(t, 25, len(po.Hints()))
}
// Helpers...