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 # Build the final Docker image
FROM alpine:3.14.3 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 COPY --from=build /k9s/execs/k9s /bin/k9s
RUN apk add --update ca-certificates \ 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) GIT_REV ?= $(shell git rev-parse --short HEAD)
SOURCE_DATE_EPOCH ?= $(shell date +%s) SOURCE_DATE_EPOCH ?= $(shell date +%s)
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") 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 IMG_NAME := derailed/k9s
IMAGE := ${IMG_NAME}:${VERSION} 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 module github.com/derailed/k9s
go 1.17 go 1.18
replace ( replace (
github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d 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/atotto/clipboard v0.1.4
github.com/cenkalti/backoff v2.2.1+incompatible github.com/cenkalti/backoff v2.2.1+incompatible
github.com/cenkalti/backoff/v4 v4.1.2 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/derailed/tview v0.6.6
github.com/fatih/color v1.13.0 github.com/fatih/color v1.13.0
github.com/fsnotify/fsnotify v1.5.1 github.com/fsnotify/fsnotify v1.5.1
github.com/fvbommel/sortorder v1.0.2 github.com/fvbommel/sortorder v1.0.2
github.com/gdamore/tcell/v2 v2.4.0 github.com/gdamore/tcell/v2 v2.4.0
github.com/ghodss/yaml v1.0.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/mattn/go-runewidth v0.0.13
github.com/petergtz/pegomock v2.9.0+incompatible github.com/petergtz/pegomock v2.9.0+incompatible
github.com/rakyll/hey v0.1.4 github.com/rakyll/hey v0.1.4
github.com/rs/zerolog v1.26.0 github.com/rs/zerolog v1.26.0
github.com/sahilm/fuzzy v0.1.0 github.com/sahilm/fuzzy v0.1.0
github.com/spf13/cobra v1.2.1 github.com/spf13/cobra v1.4.0
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.1
golang.org/x/text v0.3.7 golang.org/x/text v0.3.7
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
helm.sh/helm/v3 v3.7.1 helm.sh/helm/v3 v3.9.0
k8s.io/api v0.22.3 k8s.io/api v0.24.2
k8s.io/apimachinery v0.22.3 k8s.io/apimachinery v0.24.2
k8s.io/cli-runtime v0.22.3 k8s.io/cli-runtime v0.24.2
k8s.io/client-go v0.22.3 k8s.io/client-go v0.24.2
k8s.io/klog/v2 v2.30.0 k8s.io/klog/v2 v2.60.1
k8s.io/kubectl v0.22.3 k8s.io/kubectl v0.24.2
k8s.io/metrics v0.22.3 k8s.io/metrics v0.24.2
sigs.k8s.io/yaml v1.3.0 sigs.k8s.io/yaml v1.3.0
) )
require ( 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-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // 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 v0.11.20 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.13 // 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/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // 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/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect
github.com/Masterminds/squirrel v1.5.0 // indirect github.com/Masterminds/squirrel v1.5.2 // indirect
github.com/Microsoft/go-winio v0.4.17 // indirect github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/Microsoft/hcsshim v0.8.21 // indirect github.com/Microsoft/hcsshim v0.9.2 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
github.com/aws/aws-sdk-go v1.35.21 // indirect github.com/aws/aws-sdk-go v1.35.21 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/containerd/containerd v1.5.7 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
github.com/containerd/continuity v0.1.0 // indirect github.com/containerd/containerd v1.6.3 // indirect
github.com/cyphar/filepath-securejoin v0.2.2 // 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/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.7+incompatible // indirect github.com/docker/cli v20.10.11+incompatible // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible // indirect github.com/docker/docker v20.10.14+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.3 // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // 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/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/camelcase v1.0.0 // 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/gdamore/encoding v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // 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/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect github.com/go-openapi/swag v0.19.14 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // 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/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.1 // indirect github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.5 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/gofuzz v1.1.0 // 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/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.2.0 // 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/gorilla/mux v1.8.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // 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/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.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/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.11.13 // indirect github.com/klauspost/compress v1.13.6 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // 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/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.7.6 // 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/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // 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/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/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // 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/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/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // 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/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/opencontainers/runc v1.0.2 // indirect github.com/opencontainers/runc v1.1.1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // 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/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.6.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.2.0 // 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/russross/blackfriday v1.5.2 // indirect
github.com/shopspring/decimal v1.2.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect
github.com/sirupsen/logrus v1.8.1 // 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/spf13/pflag v1.0.5 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 // indirect golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/term v0.0.0-20210406210042-72f3dc4e9b72 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
google.golang.org/grpc v1.38.0 // indirect google.golang.org/grpc v1.43.0 // indirect
google.golang.org/protobuf v1.26.0 // indirect google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/gorp.v1 v1.7.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/apiextensions-apiserver v0.22.1 // indirect k8s.io/apiextensions-apiserver v0.24.0 // indirect
k8s.io/apiserver v0.22.1 // indirect k8s.io/apiserver v0.24.0 // indirect
k8s.io/component-base v0.22.3 // indirect k8s.io/component-base v0.24.2 // indirect
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
oras.land/oras-go v0.4.0 // indirect oras.land/oras-go v1.1.1 // indirect
sigs.k8s.io/kustomize/api v0.8.11 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
sigs.k8s.io/kustomize/kyaml v0.11.0 // indirect sigs.k8s.io/kustomize/api v0.11.4 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // 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 { func (k *K9s) GetScreenDumpDir() string {
screenDumpDir := k.ScreenDumpDir screenDumpDir := k.ScreenDumpDir
if k.manualScreenDumpDir != nil && *k.manualScreenDumpDir != "" { if k.manualScreenDumpDir != nil && *k.manualScreenDumpDir != "" {
screenDumpDir = *k.manualScreenDumpDir screenDumpDir = *k.manualScreenDumpDir
} }

View File

@ -2,10 +2,8 @@ package dao
import ( import (
"context" "context"
"fmt"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -28,21 +26,17 @@ func (h *HorizontalPodAutoscaler) List(ctx context.Context, ns string) ([]runtim
lsel = sel.AsSelector() lsel = sel.AsSelector()
} }
gvrs := []string{ rev, err := h.Factory.Client().ServerVersion()
"autoscaling/v2beta2/horizontalpodautoscalers", if err != nil {
"autoscaling/v2beta1/horizontalpodautoscalers", return nil, err
"autoscaling/v1/horizontalpodautoscalers",
} }
for _, gvr := range gvrs { gvr := "autoscaling/v1/horizontalpodautoscalers"
oo, err := h.list(gvr, ns, lsel) if rev.Minor >= "23" {
if err == nil && len(oo) > 0 { gvr = "autoscaling/v2/horizontalpodautoscalers"
return oo, nil
}
} }
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) { 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. // Get returns a node resource.
func (n *Node) Get(ctx context.Context, path string) (runtime.Object, error) { 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 { if err != nil {
return o, err return nil, err
} }
u, ok := o.(*unstructured.Unstructured) var raw *unstructured.Unstructured
if !ok { for _, o := range oo {
return nil, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o) 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 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) 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. // 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/v1/cronjobs"): &CronJob{},
client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{}, client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{},
client.NewGVR("batch/v1/jobs"): &Job{}, client.NewGVR("batch/v1/jobs"): &Job{},
client.NewGVR("v1/namespaces"): &Namespace{},
// BOZO!! Revamp with latest... // BOZO!! Revamp with latest...
// client.NewGVR("openfaas"): &OpenFaas{}, // client.NewGVR("openfaas"): &OpenFaas{},
client.NewGVR("popeye"): &Popeye{}, 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.ID = client.FQN(nns, name)
r.Fields = make(Fields, 0, len(g.Header(ns))) r.Fields = make(Fields, 0, len(g.Header(ns)))
r.Fields = append(r.Fields, nns) r.Fields = append(r.Fields, nns)
var ageCell interface{} var duration interface{}
for i, c := range row.Cells { for i, c := range row.Cells {
if g.ageIndex > 0 && i == g.ageIndex { if g.ageIndex > 0 && i == g.ageIndex {
ageCell = c duration = c
continue continue
} }
if c == nil { 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)) r.Fields = append(r.Fields, fmt.Sprintf("%v", c))
} }
if ageCell != nil { if d, ok := duration.(string); ok {
r.Fields = append(r.Fields, fmt.Sprintf("%v", ageCell)) r.Fields = append(r.Fields, d)
} }
return nil return nil

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package render package render
import ( import (
"fmt"
"testing" "testing"
"time" "time"
@ -54,17 +55,18 @@ func TestLabelize(t *testing.T) {
func TestDurationToSecond(t *testing.T) { func TestDurationToSecond(t *testing.T) {
uu := map[string]struct { uu := map[string]struct {
s, e string s string
e int64
}{ }{
"seconds": {s: "22s", e: "22"}, "seconds": {s: "22s", e: 22},
"minutes": {s: "22m", e: "1320"}, "minutes": {s: "22m", e: 1320},
"hours": {s: "12h", e: "43200"}, "hours": {s: "12h", e: 43200},
"days": {s: "3d", e: "259200"}, "days": {s: "3d", e: 259200},
"day_hour": {s: "3d9h", e: "291600"}, "day_hour": {s: "3d9h", e: 291600},
"day_hour_minute": {s: "2d22h3m", e: "252180"}, "day_hour_minute": {s: "2d22h3m", e: 252180},
"day_hour_minute_seconds": {s: "2d22h3m50s", e: "252230"}, "day_hour_minute_seconds": {s: "2d22h3m50s", e: 252230},
"year": {s: "3y", e: "94608000"}, "year": {s: "3y", e: 94608000},
"year_day": {s: "1y2d", e: "31708800"}, "year_day": {s: "1y2d", e: 31708800},
} }
for k := range uu { for k := range uu {
@ -90,36 +92,42 @@ func TestToAge(t *testing.T) {
t time.Time t time.Time
e string e string
}{ }{
"zero": {
t: time.Time{},
e: UnknownValue,
},
"good": { "good": {
t: time.Now().Add(-10 * time.Second), t: testTime().Add(-10 * time.Second),
e: "10", e: "3y196d",
}, },
} }
for k := range uu { for k := range uu {
uc := uu[k] uc := uu[k]
t.Run(k, func(t *testing.T) { 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 { uu := map[string]struct {
t time.Time t, e string
e string
}{ }{
"blank": {
t: "",
e: UnknownValue,
},
"good": { "good": {
t: time.Now().Add(-10 * time.Second), t: time.Now().Add(-10 * time.Second).Format(time.RFC3339Nano),
e: "10", e: "10s",
}, },
} }
for k := range uu { for k := range uu {
uc := uu[k] u := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
ti := toAge(metav1.Time{Time: uc.t}) assert.Equal(t, u.e, toAgeHuman(u.t))
assert.Equal(t, uc.e, toAgeHuman(ti)[:2])
}) })
} }
} }
@ -376,7 +384,7 @@ func BenchmarkMapToStr(b *testing.B) {
func TestRunesToNum(t *testing.T) { func TestRunesToNum(t *testing.T) {
uu := map[string]struct { uu := map[string]struct {
rr []rune rr []rune
e int e int64
}{ }{
"0": { "0": {
rr: []rune(""), rr: []rune(""),
@ -466,3 +474,13 @@ func BenchmarkIntToStr(b *testing.B) {
IntToStr(v) 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/k9s/internal/client"
"github.com/derailed/tview" "github.com/derailed/tview"
autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv1 "k8s.io/api/autoscaling/v1"
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" autoscalingv2 "k8s.io/api/autoscaling/v2"
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -46,10 +45,8 @@ func (h HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error
switch v { switch v {
case "autoscaling/v1": case "autoscaling/v1":
return h.renderV1(raw, ns, r) return h.renderV1(raw, ns, r)
case "autoscaling/v2beta1": case "autoscaling/v2":
return h.renderV2b1(raw, ns, r) return h.renderV2(raw, ns, r)
case "autoscaling/v2beta2":
return h.renderV2b2(raw, ns, r)
default: default:
return fmt.Errorf("Unhandled HPA version %q", v) return fmt.Errorf("Unhandled HPA version %q", v)
} }
@ -78,8 +75,8 @@ func (h HorizontalPodAutoscaler) renderV1(raw *unstructured.Unstructured, _ stri
return nil return nil
} }
func (h HorizontalPodAutoscaler) renderV2b1(raw *unstructured.Unstructured, _ string, r *Row) error { func (h HorizontalPodAutoscaler) renderV2(raw *unstructured.Unstructured, _ string, r *Row) error {
var hpa autoscalingv2beta1.HorizontalPodAutoscaler var hpa autoscalingv2.HorizontalPodAutoscaler
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &hpa) err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &hpa)
if err != nil { if err != nil {
return err return err
@ -90,30 +87,7 @@ func (h HorizontalPodAutoscaler) renderV2b1(raw *unstructured.Unstructured, _ st
hpa.Namespace, hpa.Namespace,
hpa.ObjectMeta.Name, hpa.ObjectMeta.Name,
hpa.Spec.ScaleTargetRef.Name, hpa.Spec.ScaleTargetRef.Name,
toMetricsV2b1(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)),
"",
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),
strconv.Itoa(int(*hpa.Spec.MinReplicas)), strconv.Itoa(int(*hpa.Spec.MinReplicas)),
strconv.Itoa(int(hpa.Spec.MaxReplicas)), strconv.Itoa(int(hpa.Spec.MaxReplicas)),
strconv.Itoa(int(hpa.Status.CurrentReplicas)), strconv.Itoa(int(hpa.Status.CurrentReplicas)),
@ -140,30 +114,7 @@ func toMetricsV1(spec autoscalingv1.HorizontalPodAutoscalerSpec, status autoscal
return current + "/" + target + "%" return current + "/" + target + "%"
} }
func toMetricsV2b1(specs []autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string { func toMetricsV2(specs []autoscalingv2.MetricSpec, statuses []autoscalingv2.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 {
if len(specs) == 0 { if len(specs) == 0 {
return MissingValue return MissingValue
} }
@ -174,20 +125,20 @@ func toMetricsV2b2(specs []autoscalingv2beta2.MetricSpec, statuses []autoscaling
// nolint:exhaustive // nolint:exhaustive
switch spec.Type { switch spec.Type {
case autoscalingv2beta2.ExternalMetricSourceType: case autoscalingv2.ExternalMetricSourceType:
list = append(list, externalMetricsV2b2(i, spec, statuses)) list = append(list, externalMetricsV2(i, spec, statuses))
case autoscalingv2beta2.PodsMetricSourceType: case autoscalingv2.PodsMetricSourceType:
if len(statuses) > i && statuses[i].Pods != nil { if len(statuses) > i && statuses[i].Pods != nil {
current = statuses[i].Pods.Current.AverageValue.String() current = statuses[i].Pods.Current.AverageValue.String()
} }
list = append(list, current+"/"+spec.Pods.Target.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 { if len(statuses) > i && statuses[i].Object != nil {
current = statuses[i].Object.Current.Value.String() current = statuses[i].Object.Current.Value.String()
} }
list = append(list, current+"/"+spec.Object.Target.Value.String()) list = append(list, current+"/"+spec.Object.Target.Value.String())
case autoscalingv2beta2.ResourceMetricSourceType: case autoscalingv2.ResourceMetricSourceType:
list = append(list, resourceMetricsV2b2(i, spec, statuses)) list = append(list, resourceMetricsV2(i, spec, statuses))
default: default:
list = append(list, "<unknown type>") list = append(list, "<unknown type>")
} }
@ -206,31 +157,31 @@ func toMetricsV2b2(specs []autoscalingv2beta2.MetricSpec, statuses []autoscaling
return ret 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>" current := "<unknown>"
// nolint:exhaustive // nolint:exhaustive
switch spec.Type { switch spec.Type {
case autoscalingv2beta1.ExternalMetricSourceType: case autoscalingv2.ExternalMetricSourceType:
return externalMetricsV2b1(i, spec, statuses) return externalMetricsV2(i, spec, statuses)
case autoscalingv2beta1.PodsMetricSourceType: case autoscalingv2.PodsMetricSourceType:
if len(statuses) > i && statuses[i].Pods != nil { 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() return current + "/" + spec.Pods.Target.AverageValue.String()
case autoscalingv2beta1.ObjectMetricSourceType: case autoscalingv2.ObjectMetricSourceType:
if len(statuses) > i && statuses[i].Object != nil { 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() return current + "/" + spec.Object.Target.Value.String()
case autoscalingv2beta1.ResourceMetricSourceType: case autoscalingv2.ResourceMetricSourceType:
return resourceMetricsV2b1(i, spec, statuses) return resourceMetricsV2(i, spec, statuses)
} }
return "<unknown type>" 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>" current := "<unknown>"
if spec.External.Target.AverageValue != nil { 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() 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>" current := "<unknown>"
if spec.Resource.Target.AverageValue != nil { if spec.Resource.Target.AverageValue != nil {
@ -268,48 +219,48 @@ func resourceMetricsV2b2(i int, spec autoscalingv2beta2.MetricSpec, statuses []a
return current + "/" + target return current + "/" + target
} }
func externalMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string { // func externalMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
current := "<unknown>" // current := "<unknown>"
if spec.External.TargetAverageValue != nil { // if spec.External.TargetAverageValue != nil {
if len(statuses) > i && statuses[i].External != nil && statuses[i].External.CurrentAverageValue != nil { // if len(statuses) > i && statuses[i].External != nil && statuses[i].External.CurrentAverageValue != nil {
current = statuses[i].External.CurrentAverageValue.String() // current = statuses[i].External.CurrentAverageValue.String()
} // }
return current + "/" + spec.External.TargetAverageValue.String() + " (avg)" // return current + "/" + spec.External.TargetAverageValue.String() + " (avg)"
} // }
if len(statuses) > i && statuses[i].External != nil { // if len(statuses) > i && statuses[i].External != nil {
current = statuses[i].External.CurrentValue.String() // 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 { // func resourceMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
current := "<unknown>" // current := "<unknown>"
if status := checkTargetMetricsV2b1(i, spec, statuses); status != "" { // if status := checkTargetMetricsV2b1(i, spec, statuses); status != "" {
return status // return status
} // }
if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.CurrentAverageUtilization != nil { // if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.CurrentAverageUtilization != nil {
current = IntToStr(int(*statuses[i].Resource.CurrentAverageUtilization)) // current = IntToStr(int(*statuses[i].Resource.CurrentAverageUtilization))
} // }
target := "<auto>" // target := "<auto>"
if spec.Resource.TargetAverageUtilization != nil { // if spec.Resource.TargetAverageUtilization != nil {
target = IntToStr(int(*spec.Resource.TargetAverageUtilization)) // target = IntToStr(int(*spec.Resource.TargetAverageUtilization))
} // }
return current + "/" + target // return current + "/" + target
} // }
func checkTargetMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string { // func checkTargetMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
if spec.Resource.TargetAverageValue == nil { // if spec.Resource.TargetAverageValue == nil {
return "" // return ""
} // }
var current string // var current string
if len(statuses) > i && statuses[i].Resource != nil { // if len(statuses) > i && statuses[i].Resource != nil {
current = statuses[i].Resource.CurrentAverageValue.String() // current = statuses[i].Resource.CurrentAverageValue.String()
} // }
return current + "/" + spec.Resource.TargetAverageValue.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: "VALID", Wide: true},
HeaderColumn{Name: "NOMINATED NODE", Wide: true}, HeaderColumn{Name: "NOMINATED NODE", Wide: true},
HeaderColumn{Name: "READINESS GATES", 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 ( import (
"reflect" "reflect"
"sort" "sort"
"strconv"
"strings" "strings"
"time"
"github.com/fvbommel/sortorder" "github.com/fvbommel/sortorder"
) )
@ -177,32 +175,33 @@ func (s RowSorter) Swap(i, j int) {
} }
func (s RowSorter) Less(i, j int) bool { 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... // 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. // Less return true if c1 < c2.
func Less(asc, isNumber, isDuration bool, c1, c2 string) bool { func Less(asc, isNumber, isDuration bool, id1, id2, v1, v2 string) bool {
if isNumber { var less bool
c1, c2 = strings.Replace(c1, ",", "", -1), strings.Replace(c2, ",", "", -1) 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 { 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 return 0, false
} }
type tuple struct {
field, id string
}
// Sort rows based on column index and order. // Sort rows based on column index and order.
func (r RowEvents) Sort(ns string, sortCol int, isDuration, numCol, asc bool) { func (r RowEvents) Sort(ns string, sortCol int, isDuration, numCol, asc bool) {
if sortCol == -1 { if sortCol == -1 {
@ -210,24 +214,6 @@ func (r RowEvents) Sort(ns string, sortCol int, isDuration, numCol, asc bool) {
IsDuration: isDuration, IsDuration: isDuration,
} }
sort.Sort(t) 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 { func (r RowEventSorter) Less(i, j int) bool {
f1, f2 := r.Events[i].Row.Fields, r.Events[j].Row.Fields 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 ( import (
"testing" "testing"
"time"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -409,39 +410,24 @@ func TestRowEventsDelete(t *testing.T) {
func TestRowEventsSort(t *testing.T) { func TestRowEventsSort(t *testing.T) {
uu := map[string]struct { uu := map[string]struct {
re render.RowEvents re render.RowEvents
col int col int
age, num, asc bool duration, num, asc bool
e render.RowEvents e render.RowEvents
}{ }{
"age_time": { "age_time": {
re: render.RowEvents{ re: render.RowEvents{
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "1h10m10.5s"}}}, {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", "10.5s"}}}, {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", "3h20m5.2s"}}}, {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", testTime().String()}}},
}, },
col: 2, col: 2,
asc: true, asc: true,
age: true, duration: true,
e: render.RowEvents{ e: render.RowEvents{
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "10.5s"}}}, {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", testTime().String()}}},
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "1h10m10.5s"}}}, {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", "3h20m5.2s"}}}, {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", testTime().Add(20 * time.Second).String()}}},
},
},
"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"}}},
}, },
}, },
"col0": { "col0": {
@ -483,7 +469,7 @@ func TestRowEventsSort(t *testing.T) {
for k := range uu { for k := range uu {
u := uu[k] u := uu[k]
t.Run(k, func(t *testing.T) { 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) assert.Equal(t, u.e, u.re)
}) })
} }

View File

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

View File

@ -26,9 +26,6 @@ func ComputeMaxColumns(pads MaxyPad, sortColName string, header render.Header, e
var row int var row int
for _, e := range ee { for _, e := range ee {
for index, field := range e.Row.Fields { for index, field := range e.Row.Fields {
if header.IsTimeCol(index) {
field = toAgeHuman(field)
}
width := len(field) + colPadding width := len(field) + colPadding
if index < len(pads) && width > pads[index] { if index < len(pads) && width > pads[index] {
pads[index] = width pads[index] = width
@ -60,10 +57,10 @@ func Pad(s string, width int) string {
} }
func toAgeHuman(s string) string { func toAgeHuman(s string) string {
d, err := time.ParseDuration(s) t, err := time.Parse(time.RFC3339Nano, s)
if err != nil { if err != nil {
return render.NAValue 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 { if !broadcast {
s.SetSelectionChangedFunc(nil) s.SetSelectionChangedFunc(nil)
} }
if s.model.Count() > 0 && r >= s.model.Count() { if c := s.model.Count(); c > 0 && r-1 > c {
r = s.model.Count() r = c + 1
} }
defer s.SetSelectionChangedFunc(s.selectionChanged) defer s.SetSelectionChangedFunc(s.selectionChanged)
s.Select(r, 0) s.Select(r, 0)

View File

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

View File

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

View File

@ -6,6 +6,8 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"github.com/rs/zerolog/log"
) )
// Env represent K9s and K8s available environment variables. // 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)] v, ok := e[strings.ToUpper(key)]
if !ok { 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 b, err := strconv.ParseBool(v); err == nil {
if inverse { if inverse {

View File

@ -1,7 +1,6 @@
package view package view
import ( import (
"errors"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -17,7 +16,7 @@ func TestEnvReplace(t *testing.T) {
"simple": {arg: "$A", e: "10"}, "simple": {arg: "$A", e: "10"},
"substring": {arg: "$A and $AA", e: "10 and 20"}, "substring": {arg: "$A and $AA", e: "10 and 20"},
"with-text": {arg: "Something $A", e: "Something 10"}, "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"}, "lower": {arg: "And then $b happened", e: "And then blee happened"},
"dash": {arg: "$col0", e: "fred"}, "dash": {arg: "$col0", e: "fred"},
"mix": {arg: "$col0 and then $a but $B", e: "fred and then 10 but blee"}, "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) v := view.NewHelp(app)
assert.Nil(t, v.Init(ctx)) 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, 6, v.GetColumnCount())
assert.Equal(t, "<a>", strings.TrimSpace(v.GetCell(1, 0).Text)) assert.Equal(t, "<a>", strings.TrimSpace(v.GetCell(1, 0).Text))
assert.Equal(t, "Attach", strings.TrimSpace(v.GetCell(1, 1).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 { if l.cancelUpdates {
break break
} }
log.Debug().Msgf("FLUSH %q", string(lines[i]))
_, _ = l.ansiWriter.Write(lines[i]) _, _ = l.ansiWriter.Write(lines[i])
} }
if l.follow { if l.follow {

View File

@ -57,7 +57,7 @@ func (p *PortForwardExtender) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil return nil
} }
if p.App().factory.Forwarders().IsPodForwarded(path) { 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 return nil
} }
if err := showFwdDialog(p, podName, startFwdCB); err != 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{ aa.Add(ui.KeyActions{
ui.KeyN: ui.NewKeyAction("Show Node", p.showNode, true),
ui.KeyF: ui.NewKeyAction("Show PortForward", p.showPFCmd, true), ui.KeyF: ui.NewKeyAction("Show PortForward", p.showPFCmd, true),
ui.KeyShiftR: ui.NewKeyAction("Sort Ready", p.GetTable().SortColCmd(readyCol, true), false), ui.KeyShiftR: ui.NewKeyAction("Sort Ready", p.GetTable().SortColCmd(readyCol, true), false),
ui.KeyShiftT: ui.NewKeyAction("Sort Restart", p.GetTable().SortColCmd("RESTARTS", false), 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... // 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 { func (p *Pod) showPFCmd(evt *tcell.EventKey) *tcell.EventKey {
path := p.GetTable().GetSelectedItem() path := p.GetTable().GetSelectedItem()
if path == "" { if path == "" {

View File

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