From 4f5c1a1105974e8bbff9a0e145c9a8f6b642a21d Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Wed, 17 Sep 2025 16:49:58 -0600 Subject: [PATCH] Rel v0.50.10 (#3564) * fix #3455 * Clean up and refactor * fix #3495 #3470 #3455 - fix cmd alias and filters * fix #3535 - revert * fix #3478 - make vulscan a bit less cpu extensive (init stab...) * fix issue with plugin foreground exit * fix#3466-add shared gpu on nodes * fix #3541 - use default namespace when not specified * update deps + release notes --- Makefile | 4 +- README.md | 7 +- change_logs/release_v0.50.10.md | 82 ++++++++++++ go.mod | 39 +++--- go.sum | 98 +++++++------- internal/client/config.go | 2 +- internal/client/config_test.go | 2 +- internal/client/gvr.go | 4 +- internal/client/gvrs.go | 27 ++++ internal/client/testdata/config.1 | 1 - internal/config/alias.go | 28 ++-- internal/config/alias_test.go | 126 ++++++++++++++++++ internal/config/k9s.go | 7 +- internal/config/testdata/configs/default.yaml | 2 +- internal/dao/alias.go | 5 - internal/dao/alias_test.go | 85 ------------ internal/dao/rbac_policy.go | 12 +- internal/dao/rbac_policy_test.go | 6 +- internal/model/cmd_buff.go | 10 +- internal/model/fish_buff.go | 2 +- internal/model/stack_test.go | 20 +-- internal/model/types.go | 8 +- internal/port/pfs.go | 5 +- internal/port/pfs_test.go | 3 +- internal/port/tunnel.go | 10 +- internal/render/cronjob_test.go | 2 +- internal/render/dp_test.go | 2 +- internal/render/ds_test.go | 2 +- internal/render/helpers.go | 7 +- internal/render/job_test.go | 2 +- internal/render/node.go | 46 ++++++- internal/render/node_int_test.go | 73 ++++++++++ internal/render/pod_test.go | 6 +- internal/render/rs_test.go | 2 +- internal/render/sts_test.go | 2 +- internal/slogs/keys.go | 1 + internal/ui/app_test.go | 8 +- internal/ui/crumbs_test.go | 20 +-- internal/ui/prompt.go | 10 +- internal/ui/prompt_test.go | 2 +- internal/ui/table.go | 2 +- internal/ui/table_helper.go | 4 +- internal/ui/table_helper_test.go | 4 +- internal/view/actions.go | 15 +-- internal/view/app.go | 13 +- internal/view/app_test.go | 2 +- internal/view/browser.go | 12 +- internal/view/cmd/args.go | 35 ++++- internal/view/cmd/args_test.go | 2 +- internal/view/cmd/helpers.go | 29 +++- internal/view/cmd/interpreter.go | 57 +++++++- internal/view/cmd/interpreter_test.go | 93 ++++++++++--- internal/view/cmd/types.go | 23 +++- internal/view/command.go | 41 +++--- internal/view/command_test.go | 25 +++- internal/view/details.go | 6 +- internal/view/dir.go | 4 +- internal/view/exec.go | 25 +++- internal/view/help.go | 6 +- internal/view/helpers.go | 4 +- internal/view/live_view.go | 6 +- internal/view/log.go | 6 +- internal/view/pf_extender.go | 2 +- internal/view/picker.go | 9 +- internal/view/pulse.go | 10 +- internal/view/table_int_test.go | 2 +- internal/view/workload.go | 2 +- internal/view/xray.go | 10 +- internal/vul/scan.go | 5 +- internal/vul/scanner.go | 34 ++--- snap/snapcraft.yaml | 2 +- 71 files changed, 879 insertions(+), 391 deletions(-) create mode 100644 change_logs/release_v0.50.10.md diff --git a/Makefile b/Makefile index 5ee7de08..322022de 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ NAME := k9s -VERSION ?= v0.50.9 +VERSION ?= v0.50.10 PACKAGE := github.com/derailed/$(NAME) OUTPUT_BIN ?= execs/${NAME} GO_FLAGS ?= @@ -13,7 +13,7 @@ BUILD_PLATFORMS ?= linux/amd64,linux/arm64 SOURCE_DATE_EPOCH ?= $(shell date +%s) ifeq ($(shell uname), Darwin) -DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") +DATE ?= $(shell TZ=UTC /bin/date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif diff --git a/README.md b/README.md index b81f9214..defac15b 100644 --- a/README.md +++ b/README.md @@ -403,8 +403,13 @@ You can now override the context portForward default address configuration by se liveViewAutoRefresh: false # !!New!! v0.50.8... # Extends the list of supported GPU vendors. The key is the vendor name, the value must correspond to k8s resource driver designation. + # Default known GPU vendors: + # nvidia: nvidia.com/gpu + # nvidia-shared: nvidia.com/gpu.shared + # amd: amd.com/gpu + # intel: gpu.intel.com/i915 gpuVendors: - bozo: bozo/gpu + bozo: bozo/gpu # extends the gpu vendor and add "bozo" # The path to screen dump. Default: '%temp_dir%/k9s-screens-%username%' (k9s info) screenDumpDir: /tmp/dumps # Represents ui poll intervals in seconds. Default 2.0 secs. Minimum value is 2.0 - values below will be capped to the minimum. diff --git a/change_logs/release_v0.50.10.md b/change_logs/release_v0.50.10.md new file mode 100644 index 00000000..a3d8189b --- /dev/null +++ b/change_logs/release_v0.50.10.md @@ -0,0 +1,82 @@ + + +# Release v0.50.10 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA) + +## Maintenance Release! + +--- + +## A Word From Our Sponsors... + +To all the good folks below that opted to `pay it forward` and join our sponsorship program, I salute you!! + +* [rufusshrestha](https://github.com/rufusshrestha) +* [Ovidijus Balkauskas](https://github.com/Stogas) +* [Konrad Konieczny](https://github.com/Psyhackological) +* [Serit Tromsø](https://github.com/serit) +* [Dennis](https://github.com/dennisTGC) +* [LinPr](https://github.com/LinPr) +* [franzXaver987](https://github.com/franzXaver987) +* [Drew Showalter](https://github.com/one19) +* [Sandylen](https://github.com/Sandylen) +* [Uriah Carpenter](https://github.com/uriahcarpenter) +* [Vector Group](https://github.com/vectorgrp) +* [Stefan Roman](https://github.com/katapultcloud) +* [Phillip](https://github.com/Loki-Afro) +* [Lasse Bang Mikkelsen](https://github.com/lassebm) + +> Sponsorship cancellations since the last release: **19!** 🥹 + +--- + +## Resolved Issues + +* [#3541](https://github.com/derailed/k9s/issues/3541) ServiceAccount RBAC Rules not displayed if RoleBinding subject doesn't specify namespace +* [#3535](https://github.com/derailed/k9s/issues/3535) Current Release process will cause code changes been reverted +* [#3525](https://github.com/derailed/k9s/issues/3525) k9s suspends when launching foreground plugin +* [#3495](https://github.com/derailed/k9s/issues/3495) Regression: filtering no long works with aliases +* [#3478](https://github.com/derailed/k9s/issues/3478) High Disk and CPU usage when imageScans Is enabled in K9s +* [#3470](https://github.com/derailed/k9s/issues/3470) Aliases for pods with unequal (!=) label filters not working +* [#3466](https://github.com/derailed/k9s/issues/3466) Shared GPU (nvidia.com/gpu.shared) is shown as n/a on K9s node view +* [#3455](https://github.com/derailed/k9s/issues/3455) memory command not found + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#3558](https://github.com/derailed/k9s/pull/3558) refactor(duplik8s): consolidate duplicate resource commands and updat… +* [#3555](https://github.com/derailed/k9s/pull/3555) feat: add dup plugin +* [#3543](https://github.com/derailed/k9s/pull/3543) Make "flux trace" more generic +* [#3536](https://github.com/derailed/k9s/pull/3536) Add flux-operator resources to flux plugin +* [#3528](https://github.com/derailed/k9s/pull/3528) feat(plugins): add pvc debug container plugin +* [#3517](https://github.com/derailed/k9s/pull/3517) Feature/refresh rate +* [#3516](https://github.com/derailed/k9s/pull/3516) Fixes flickering/jumping issue in context suggestions caused by inconsistent spacing behavior +* [#3515](https://github.com/derailed/k9s/pull/3515) Fix/suppress init no resources warning +* [#3513](https://github.com/derailed/k9s/pull/3513) fix: Color PV row according to its STATUS column +* [#3513](https://github.com/derailed/k9s/pull/3513) fix: Color PV row according to its STATUS column +* [#3505](https://github.com/derailed/k9s/pull/3505) docs: Add installation method with gah +* [#3503](https://github.com/derailed/k9s/pull/3503) fix(logs): enhance log streaming with retry mechanism and error handling +* [#3489](https://github.com/derailed/k9s/pull/3489) feat: Add context deletion functionality +* [#3487](https://github.com/derailed/k9s/pull/3487) fsupport core group resources in k9s/plugins/watch-events.yaml +* [#3485](https://github.com/derailed/k9s/pull/3485) Add disable-self-subject-access-reviews flag to disable can-i check… +* [#3464](https://github.com/derailed/k9s/pull/3464) fix: get-all command in get all plugin + +--- + © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)# \ No newline at end of file diff --git a/go.mod b/go.mod index 66020dd9..25e1c815 100644 --- a/go.mod +++ b/go.mod @@ -29,20 +29,20 @@ require ( golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b golang.org/x/text v0.29.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.18.6 + helm.sh/helm/v3 v3.19.0 k8s.io/api v0.34.1 - k8s.io/apiextensions-apiserver v0.33.3 + k8s.io/apiextensions-apiserver v0.34.1 k8s.io/apimachinery v0.34.1 k8s.io/cli-runtime v0.34.1 k8s.io/client-go v0.34.1 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.33.4 - k8s.io/metrics v0.33.4 + k8s.io/kubectl v0.34.1 + k8s.io/metrics v0.34.1 sigs.k8s.io/yaml v1.6.0 ) require ( - cel.dev/expr v0.19.1 // indirect + cel.dev/expr v0.24.0 // indirect cloud.google.com/go v0.116.0 // indirect cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect @@ -57,7 +57,7 @@ require ( github.com/BurntSushi/toml v1.5.0 // indirect github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect github.com/DataDog/zstd v1.5.5 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect @@ -120,7 +120,6 @@ require ( github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/sevenzip v1.6.0 // indirect github.com/bodgit/windows v1.0.1 // indirect - github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect @@ -129,7 +128,7 @@ require ( github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect - github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect + github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/containerd v1.7.28 // indirect github.com/containerd/containerd/api v1.8.0 // indirect @@ -160,8 +159,8 @@ require ( github.com/elliotchance/phpserialize v1.4.0 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/envoyproxy/go-control-plane v0.13.1 // indirect - github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/facebookincubator/nvdtools v0.1.5 // indirect @@ -178,6 +177,7 @@ require ( github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/go-git/go-git/v5 v5.16.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -312,6 +312,7 @@ require ( github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/spf13/viper v1.20.1 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/sylabs/sif/v2 v2.22.0 // indirect github.com/sylabs/squashfs v1.0.6 // indirect @@ -331,10 +332,11 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/zclconf/go-cty v1.16.3 // indirect + github.com/zeebo/errs v1.4.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.31.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.36.0 // indirect go.opentelemetry.io/otel/metric v1.36.0 // indirect @@ -357,19 +359,18 @@ require ( golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.215.0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect - google.golang.org/grpc v1.68.1 // indirect - google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/grpc v1.72.1 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gorm.io/gorm v1.30.0 // indirect gotest.tools/v3 v3.4.0 // indirect - k8s.io/apiserver v0.33.3 // indirect - k8s.io/component-base v0.33.4 // indirect - k8s.io/component-helpers v0.33.4 // indirect + k8s.io/apiserver v0.34.1 // indirect + k8s.io/component-base v0.34.1 // indirect + k8s.io/component-helpers v0.34.1 // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect modernc.org/libc v1.66.3 // indirect diff --git a/go.sum b/go.sum index d25b3c64..4ed2df79 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= -cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -90,8 +90,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= @@ -260,8 +260,6 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -307,8 +305,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= -github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/containerd v1.7.28 h1:Nsgm1AtcmEh4AHAJ4gGlNSaKgXiNccU270Dnf81FQ3c= @@ -407,12 +405,16 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= -github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= -github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= -github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= -github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= @@ -483,6 +485,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -648,8 +652,8 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJr github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow= @@ -1076,6 +1080,8 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -1162,6 +1168,8 @@ github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= @@ -1178,12 +1186,12 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= -go.opentelemetry.io/contrib/detectors/gcp v1.31.0 h1:G1JQOreVrfhRkner+l4mrGxmfqYCAuy76asTDAo0xsA= -go.opentelemetry.io/contrib/detectors/gcp v1.31.0/go.mod h1:tzQL6E1l+iV44YFTkcAeNQqzXUiekSYP9jjJjXwEd00= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= @@ -1196,10 +1204,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7Z go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= @@ -1223,8 +1231,8 @@ go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRa go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= -go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -1654,10 +1662,10 @@ google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1685,11 +1693,9 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= -google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 h1:hUfOButuEtpc0UvYiaYRbNwxVYr0mQQOWq6X8beJ9Gc= -google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3/go.mod h1:jzYlkSMbKypzuu6xoAEijsNVo9ZeDF1u/zCfFgsx7jg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1739,8 +1745,8 @@ gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.18.6 h1:S/2CqcYnNfLckkHLI0VgQbxgcDaU3N4A/46E3n9wSNY= -helm.sh/helm/v3 v3.18.6/go.mod h1:L/dXDR2r539oPlFP1PJqKAC1CUgqHJDLkxKpDGrWnyg= +helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k= +helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1750,28 +1756,28 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= -k8s.io/apiextensions-apiserver v0.33.3 h1:qmOcAHN6DjfD0v9kxL5udB27SRP6SG/MTopmge3MwEs= -k8s.io/apiextensions-apiserver v0.33.3/go.mod h1:oROuctgo27mUsyp9+Obahos6CWcMISSAPzQ77CAQGz8= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= -k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M= k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE= k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= -k8s.io/component-base v0.33.4 h1:Jvb/aw/tl3pfgnJ0E0qPuYLT0NwdYs1VXXYQmSuxJGY= -k8s.io/component-base v0.33.4/go.mod h1:567TeSdixWW2Xb1yYUQ7qk5Docp2kNznKL87eygY8Rc= -k8s.io/component-helpers v0.33.4 h1:DYHQPxWB3XIk7hwAQ4YczUelJ37PcUHfnLeee0qFqV8= -k8s.io/component-helpers v0.33.4/go.mod h1:kRgidIgCKFqOW/wy7D8IL3YOT3iaIRZu6FcTEyRr7WU= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/component-helpers v0.34.1 h1:gWhH3CCdwAx5P3oJqZKb4Lg5FYZTWVbdWtOI8n9U4XY= +k8s.io/component-helpers v0.34.1/go.mod h1:4VgnUH7UA/shuBur+OWoQC0xfb69sy/93ss0ybZqm3c= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/kubectl v0.33.4 h1:nXEI6Vi+oB9hXxoAHyHisXolm/l1qutK3oZQMak4N98= -k8s.io/kubectl v0.33.4/go.mod h1:Xe7P9X4DfILvKmlBsVqUtzktkI56lEj22SJW7cFy6nE= -k8s.io/metrics v0.33.4 h1:eJ6UdTpKTUQVZbKpUdm5ve39aPpAvvNwLrs13oQcWKc= -k8s.io/metrics v0.33.4/go.mod h1:NO/lgFtyIPTurz56debdSh5qRqRfpO8MlkMpau1Ue8U= +k8s.io/kubectl v0.34.1 h1:1qP1oqT5Xc93K+H8J7ecpBjaz511gan89KO9Vbsh/OI= +k8s.io/kubectl v0.34.1/go.mod h1:JRYlhJpGPyk3dEmJ+BuBiOB9/dAvnrALJEiY/C5qa6A= +k8s.io/metrics v0.34.1 h1:374Rexmp1xxgRt64Bi0TsjAM8cA/Y8skwCoPdjtIslE= +k8s.io/metrics v0.34.1/go.mod h1:Drf5kPfk2NJrlpcNdSiAAHn/7Y9KqxpRNagByM7Ei80= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= diff --git a/internal/client/config.go b/internal/client/config.go index d3f629d0..a784187d 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -20,7 +20,7 @@ import ( const ( // DefaultCallTimeoutDuration is the default api server call timeout duration. - DefaultCallTimeoutDuration time.Duration = 15 * time.Second + DefaultCallTimeoutDuration time.Duration = 120 * time.Second // UsePersistentConfig caches client config to avoid reloads. UsePersistentConfig = true diff --git a/internal/client/config_test.go b/internal/client/config_test.go index c99a24f9..d90dea1d 100644 --- a/internal/client/config_test.go +++ b/internal/client/config_test.go @@ -32,7 +32,7 @@ func TestCallTimeout(t *testing.T) { e: 1 * time.Minute, }, "default": { - e: 15 * time.Second, + e: client.DefaultCallTimeoutDuration, }, } diff --git a/internal/client/gvr.go b/internal/client/gvr.go index 91befcaf..c6d90bf6 100644 --- a/internal/client/gvr.go +++ b/internal/client/gvr.go @@ -84,11 +84,11 @@ func NewGVR(s string) *GVR { } func (g *GVR) IsCommand() bool { - return g != nil && strings.Contains(g.raw, " ") + return !g.IsK8sRes() } func (g *GVR) IsK8sRes() bool { - return strings.Contains(g.raw, "/") + return g != nil && (strings.Contains(g.raw, "/") || reservedGVRs.Has(g)) } // WithSubResource builds a new gvr with a sub resource. diff --git a/internal/client/gvrs.go b/internal/client/gvrs.go index c4a3f912..fcb0c45c 100644 --- a/internal/client/gvrs.go +++ b/internal/client/gvrs.go @@ -1,5 +1,7 @@ package client +import "k8s.io/apimachinery/pkg/util/sets" + var ( // Apps... DpGVR = NewGVR("apps/v1/deployments") @@ -78,3 +80,28 @@ var ( RoGVR = NewGVR("rbac.authorization.k8s.io/v1/roles") RobGVR = NewGVR("rbac.authorization.k8s.io/v1/rolebindings") ) + +var reservedGVRs = sets.New( + CpuGVR, + MemGVR, + WkGVR, + CoGVR, + CtGVR, + RefGVR, + PuGVR, + ScnGVR, + DirGVR, + PfGVR, + SdGVR, + BeGVR, + AliGVR, + XGVR, + HlpGVR, + QGVR, + HmGVR, + HmhGVR, + RbacGVR, + PolGVR, + UsrGVR, + GrpGVR, +) diff --git a/internal/client/testdata/config.1 b/internal/client/testdata/config.1 index 640d0ef2..cb7b70a2 100644 --- a/internal/client/testdata/config.1 +++ b/internal/client/testdata/config.1 @@ -15,5 +15,4 @@ contexts: name: blee current-context: blee kind: Config -preferences: {} users: null diff --git a/internal/config/alias.go b/internal/config/alias.go index 2ab18bd4..d64d3ba8 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -8,6 +8,7 @@ import ( "io/fs" "log/slog" "os" + "strings" "sync" "github.com/derailed/k9s/internal/client" @@ -81,19 +82,28 @@ func (a *Aliases) Clear() { } } -func (a *Aliases) Resolve(command string) (*client.GVR, string, bool) { - agvr, ok := a.Get(command) - if !ok { - return nil, "", false - } - - p := cmd.NewInterpreter(agvr.String()) +func (a *Aliases) Resolve(p *cmd.Interpreter) (*client.GVR, bool) { gvr, ok := a.Get(p.Cmd()) if !ok { - return agvr, "", true + return nil, false } - return gvr, p.Args(), true + if gvr.IsK8sRes() { + p.Reset(strings.Replace(p.GetLine(), p.Cmd(), gvr.String(), 1)) + return gvr, true + } + + for gvr.IsCommand() { + ap := cmd.NewInterpreter(gvr.String()) + gvr, ok = a.Get(ap.Cmd()) + if !ok { + return gvr, false + } + ap.Merge(p) + p.Reset(strings.Replace(ap.GetLine(), ap.Cmd(), gvr.String(), 1)) + } + + return gvr, true } // Get retrieves an alias. diff --git a/internal/config/alias_test.go b/internal/config/alias_test.go index 9391663d..c49ddb39 100644 --- a/internal/config/alias_test.go +++ b/internal/config/alias_test.go @@ -13,6 +13,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/view/cmd" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -129,6 +130,131 @@ func TestAliasesSave(t *testing.T) { assert.Len(t, a.Alias, c) } +func TestAliasResolve(t *testing.T) { + uu := map[string]struct { + exp string + ok bool + gvr *client.GVR + cmd *cmd.Interpreter + }{ + "gvr": { + exp: "v1/pods", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods"), + }, + + "kind": { + exp: "pod", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods"), + }, + + "plural": { + exp: "pods", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods"), + }, + + "short-name": { + exp: "po", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods"), + }, + + "short-name-with-args": { + exp: "po 'a in (b,c)' @zorb bozo", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods 'a in (b,c)' @zorb bozo"), + }, + + "alias": { + exp: "pipo", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods"), + }, + + "toast-command": { + exp: "zorg", + }, + + "alias-no-args": { + exp: "wkl", + ok: true, + gvr: client.WkGVR, + cmd: cmd.NewInterpreter("workloads"), + }, + + "alias-ns-arg": { + exp: "pp", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods default"), + }, + + "multi-alias-ns-inception": { + exp: "ppo", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods a=b,b=c default"), + }, + + "full-alias": { + exp: "ppc", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods @fred app=fred default"), + }, + + "plain-filter": { + exp: "po /fred @bozo ns-1", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods /fred @bozo ns-1"), + }, + + "alias-filter": { + exp: "pipo /fred @bozo ns-1", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods /fred @bozo ns-1"), + }, + + "complex-filter": { + exp: "ppc /fred @bozo ns-1", + ok: true, + gvr: client.PodGVR, + cmd: cmd.NewInterpreter("v1/pods @bozo /fred app=fred ns-1"), + }, + } + + a := config.NewAliases() + a.Define(client.PodGVR, "po", "pipo", "pod") + a.Define(client.PodGVR, client.PodGVR.String()) + a.Define(client.PodGVR, client.PodGVR.AsResourceName()) + a.Define(client.WkGVR, client.WkGVR.String(), "workload", "wkl") + a.Define(client.NewGVR("pod default"), "pp") + a.Define(client.NewGVR("pipo a=b,b=c default"), "ppo") + a.Define(client.NewGVR("pod default app=fred @fred"), "ppc") + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.exp) + gvr, ok := a.Resolve(p) + assert.Equal(t, u.ok, ok) + if ok { + assert.Equal(t, u.gvr, gvr) + assert.Equal(t, u.cmd.GetLine(), p.GetLine()) + } + }) + } +} + // Helpers... var ( diff --git a/internal/config/k9s.go b/internal/config/k9s.go index fc186585..9c02585d 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -26,9 +26,10 @@ type gpuVendors map[string]string var KnownGPUVendors = defaultGPUVendors var defaultGPUVendors = gpuVendors{ - "nvidia": "nvidia.com/gpu", - "amd": "amd.com/gpu", - "intel": "gpu.intel.com/i915", + "nvidia": "nvidia.com/gpu", + "nvidia-shared": "nvidia.com/gpu.shared", + "amd": "amd.com/gpu", + "intel": "gpu.intel.com/i915", } // K9s tracks K9s configuration options. diff --git a/internal/config/testdata/configs/default.yaml b/internal/config/testdata/configs/default.yaml index 90d200e0..3cd022ba 100644 --- a/internal/config/testdata/configs/default.yaml +++ b/internal/config/testdata/configs/default.yaml @@ -3,7 +3,7 @@ k9s: gpuVendors: {} screenDumpDir: /tmp/k9s-test/screen-dumps refreshRate: 2 - apiServerTimeout: 15s + apiServerTimeout: 2m0s maxConnRetry: 5 readOnly: false noExitOnCtrlC: false diff --git a/internal/dao/alias.go b/internal/dao/alias.go index 651d22a1..9d24ab44 100644 --- a/internal/dao/alias.go +++ b/internal/dao/alias.go @@ -61,11 +61,6 @@ func (*Alias) List(ctx context.Context, _ string) ([]runtime.Object, error) { return oo, nil } -// AsGVR returns a matching gvr if it exists. -func (a *Alias) AsGVR(alias string) (*client.GVR, string, bool) { - return a.Resolve(alias) -} - // Get fetch a resource. func (*Alias) Get(_ context.Context, _ string) (runtime.Object, error) { return nil, errors.New("nyi") diff --git a/internal/dao/alias_test.go b/internal/dao/alias_test.go index 6738f5df..901fb9cf 100644 --- a/internal/dao/alias_test.go +++ b/internal/dao/alias_test.go @@ -16,91 +16,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestAsGVR(t *testing.T) { - a := dao.NewAlias(makeFactory()) - a.Define(client.PodGVR, "po", "pipo", "pod") - a.Define(client.PodGVR, client.PodGVR.String()) - a.Define(client.PodGVR, client.PodGVR.AsResourceName()) - a.Define(client.WkGVR, client.WkGVR.String(), "workload", "wkl") - a.Define(client.NewGVR("pod default"), "pp") - a.Define(client.NewGVR("pipo default"), "ppo") - a.Define(client.NewGVR("pod default app=fred @fred"), "ppc") - - uu := map[string]struct { - cmd string - ok bool - gvr *client.GVR - exp string - }{ - "gvr": { - cmd: "v1/pods", - ok: true, - gvr: client.PodGVR, - }, - - "r": { - cmd: "pods", - ok: true, - gvr: client.PodGVR, - }, - - "alias1": { - cmd: "po", - ok: true, - gvr: client.PodGVR, - }, - - "alias-2": { - cmd: "pipo", - ok: true, - gvr: client.PodGVR, - }, - - "missing": { - cmd: "zorg", - }, - - "no-args": { - cmd: "wkl", - ok: true, - gvr: client.WkGVR, - }, - - "ns-arg": { - cmd: "pp", - ok: true, - gvr: client.PodGVR, - exp: "default", - }, - - "ns-inception": { - cmd: "ppo", - ok: true, - gvr: client.PodGVR, - exp: "default", - }, - - "full-alias": { - cmd: "ppc", - ok: true, - gvr: client.PodGVR, - exp: "default app=fred @fred", - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - gvr, exp, ok := a.AsGVR(u.cmd) - assert.Equal(t, u.ok, ok) - if u.ok { - assert.Equal(t, u.gvr, gvr) - assert.Equal(t, u.exp, exp) - } - }) - } -} - func TestAliasList(t *testing.T) { a := dao.Alias{} a.Init(makeFactory(), client.AliGVR) diff --git a/internal/dao/rbac_policy.go b/internal/dao/rbac_policy.go index 4dd89bff..26a7dbba 100644 --- a/internal/dao/rbac_policy.go +++ b/internal/dao/rbac_policy.go @@ -69,7 +69,7 @@ func (p *Policy) loadClusterRoleBinding(kind, name string) (render.Policies, err var nn []string for i := range crbs { for _, s := range crbs[i].Subjects { - if isSameSubject(kind, ns, n, &s) { + if isSameSubject(kind, ns, crbs[i].Namespace, n, &s) { nn = append(nn, crbs[i].RoleRef.Name) } } @@ -174,7 +174,7 @@ func (p *Policy) fetchRoleBindingNamespaces(kind, name string) (map[string]strin ss := make(map[string]string, len(rbs)) for i := range rbs { for _, s := range rbs[i].Subjects { - if isSameSubject(kind, ns, n, &s) { + if isSameSubject(kind, ns, rbs[i].Namespace, n, &s) { ss[rbs[i].RoleRef.Kind+":"+rbs[i].RoleRef.Name] = rbs[i].Namespace } } @@ -186,13 +186,17 @@ func (p *Policy) fetchRoleBindingNamespaces(kind, name string) (map[string]strin // isSameSubject verifies if the incoming type name and namespace match a subject from a // cluster/roleBinding. A ServiceAccount will always have a namespace and needs to be validated to ensure // we don't display permissions for a ServiceAccount with the same name in a different namespace -func isSameSubject(kind, ns, name string, subject *rbacv1.Subject) bool { +func isSameSubject(kind, ns, bns, name string, subject *rbacv1.Subject) bool { if subject.Kind != kind || subject.Name != name { return false } if kind == rbacv1.ServiceAccountKind { // Kind and name were checked above, check the namespace - return client.IsAllNamespaces(ns) || subject.Namespace == ns + cns := subject.Namespace + if cns == "" { + cns = bns + } + return client.IsAllNamespaces(ns) || cns == ns } return true } diff --git a/internal/dao/rbac_policy_test.go b/internal/dao/rbac_policy_test.go index 29967fdc..6efada25 100644 --- a/internal/dao/rbac_policy_test.go +++ b/internal/dao/rbac_policy_test.go @@ -27,6 +27,7 @@ func TestIsSameSubject(t *testing.T) { }, want: true, }, + "name-does-not-match": { kind: rbacv1.UserKind, name: "foo", @@ -36,6 +37,7 @@ func TestIsSameSubject(t *testing.T) { }, want: false, }, + "kind-does-not-match": { kind: rbacv1.GroupKind, name: "foo", @@ -45,6 +47,7 @@ func TestIsSameSubject(t *testing.T) { }, want: false, }, + "serviceAccount-all-match": { kind: rbacv1.ServiceAccountKind, name: "foo", @@ -56,6 +59,7 @@ func TestIsSameSubject(t *testing.T) { }, want: true, }, + "serviceAccount-namespace-no-match": { kind: rbacv1.ServiceAccountKind, name: "foo", @@ -72,7 +76,7 @@ func TestIsSameSubject(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - same := isSameSubject(u.kind, u.namespace, u.name, &u.subject) + same := isSameSubject(u.kind, u.namespace, u.namespace, u.name, &u.subject) assert.Equal(t, u.want, same) }) } diff --git a/internal/model/cmd_buff.go b/internal/model/cmd_buff.go index 315526bb..07fe6545 100644 --- a/internal/model/cmd_buff.go +++ b/internal/model/cmd_buff.go @@ -132,9 +132,13 @@ func (c *CmdBuff) resetCancel() { } // SetText initializes the buffer with a command. -func (c *CmdBuff) SetText(text, suggestion string) { +func (c *CmdBuff) SetText(text, suggestion string, wipe bool) { c.mx.Lock() - c.buff, c.suggestion = []rune(text), suggestion + if wipe { + c.buff, c.suggestion = []rune(text), suggestion + } else { + c.buff, c.suggestion = append(c.buff, []rune(text)...), suggestion + } c.mx.Unlock() c.fireBufferCompleted(c.GetText(), c.GetSuggestion()) } @@ -163,7 +167,7 @@ func (c *CmdBuff) Delete() { if c.Empty() { return } - c.SetText(string(c.buff[:len(c.buff)-1]), "") + c.SetText(string(c.buff[:len(c.buff)-1]), "", true) c.fireBufferChanged(c.GetText(), c.GetSuggestion()) if c.hasCancel() { return diff --git a/internal/model/fish_buff.go b/internal/model/fish_buff.go index b0df7a61..a7b9cc0f 100644 --- a/internal/model/fish_buff.go +++ b/internal/model/fish_buff.go @@ -133,5 +133,5 @@ func (f *FishBuff) fireSuggestionChanged(ss []string) { } else { suggest = ss[f.suggestionIndex] } - f.SetText(f.GetText(), suggest) + f.SetText(f.GetText(), suggest, true) } diff --git a/internal/model/stack_test.go b/internal/model/stack_test.go index 6926a152..8c9b93dd 100644 --- a/internal/model/stack_test.go +++ b/internal/model/stack_test.go @@ -305,13 +305,13 @@ func (c) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { return ni func (c) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { return nil } -func (c) SetRect(int, int, int, int) {} -func (c) GetRect() (a, b, c, d int) { return 0, 0, 0, 0 } -func (c) GetFocusable() tview.Focusable { return nil } -func (c) Focus(func(tview.Primitive)) {} -func (c) Blur() {} -func (c) Start() {} -func (c) Stop() {} -func (c) Init(context.Context) error { return nil } -func (c) SetFilter(string) {} -func (c) SetLabelSelector(labels.Selector) {} +func (c) SetRect(int, int, int, int) {} +func (c) GetRect() (a, b, c, d int) { return 0, 0, 0, 0 } +func (c) GetFocusable() tview.Focusable { return nil } +func (c) Focus(func(tview.Primitive)) {} +func (c) Blur() {} +func (c) Start() {} +func (c) Stop() {} +func (c) Init(context.Context) error { return nil } +func (c) SetFilter(string, bool) {} +func (c) SetLabelSelector(labels.Selector, bool) {} diff --git a/internal/model/types.go b/internal/model/types.go index 6f496fc0..429c6c7e 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -104,9 +104,13 @@ type Viewer interface { SetCommand(*cmd.Interpreter) } +// Filterer represents a filterable component. type Filterer interface { - SetFilter(string) - SetLabelSelector(labels.Selector) + // SetFilter sets the filter text. + SetFilter(string, bool) + + // SetLabelSelector sets the label selector. + SetLabelSelector(labels.Selector, bool) } // Cruder performs crud operations. diff --git a/internal/port/pfs.go b/internal/port/pfs.go index 533f3e46..9f72e183 100644 --- a/internal/port/pfs.go +++ b/internal/port/pfs.go @@ -4,12 +4,13 @@ package port import ( + "context" "fmt" "strings" ) // PortCheck checks if port is free on host. -type PortChecker func(PortTunnel) bool +type PortChecker func(context.Context, PortTunnel) bool // PFAnns represents a collection of port forward annotations. type PFAnns []*PFAnn @@ -39,7 +40,7 @@ func (aa PFAnns) ToTunnels(address string, _ ContainerPortSpecs, available PortC if err != nil { return pts, err } - if !available(pt) { + if !available(context.Background(), pt) { return pts, fmt.Errorf("port %s is not available on host", pt.LocalPort) } pts = append(pts, pt) diff --git a/internal/port/pfs_test.go b/internal/port/pfs_test.go index 6a7bb2a7..ab38b4f2 100644 --- a/internal/port/pfs_test.go +++ b/internal/port/pfs_test.go @@ -4,6 +4,7 @@ package port_test import ( + "context" "errors" "testing" @@ -93,7 +94,7 @@ func TestPFsToTunnel(t *testing.T) { }, } - f := func(port.PortTunnel) bool { + f := func(context.Context, port.PortTunnel) bool { return true } diff --git a/internal/port/tunnel.go b/internal/port/tunnel.go index 585228a6..d1b01478 100644 --- a/internal/port/tunnel.go +++ b/internal/port/tunnel.go @@ -4,6 +4,7 @@ package port import ( + "context" "fmt" "log/slog" "net" @@ -15,9 +16,9 @@ import ( type PortTunnels []PortTunnel // CheckAvailable checks if all port tunnels are available. -func (t PortTunnels) CheckAvailable() error { +func (t PortTunnels) CheckAvailable(ctx context.Context) error { for _, pt := range t { - if !IsPortFree(pt) { + if !IsPortFree(ctx, pt) { return fmt.Errorf("port %s is not available on host", pt.LocalPort) } } @@ -55,8 +56,9 @@ func (t PortTunnel) PortMap() string { } // IsPortFree checks if a address/port pair is available on host. -func IsPortFree(t PortTunnel) bool { - s, err := net.Listen("tcp", fmt.Sprintf("%s:%s", t.Address, t.LocalPort)) +func IsPortFree(ctx context.Context, t PortTunnel) bool { + var ncfg net.ListenConfig + s, err := ncfg.Listen(ctx, "tcp", fmt.Sprintf("%s:%s", t.Address, t.LocalPort)) if err != nil { slog.Warn("Port is not available", slogs.Port, t.LocalPort, slogs.Address, t.Address) return false diff --git a/internal/render/cronjob_test.go b/internal/render/cronjob_test.go index d15e4575..e93279c2 100644 --- a/internal/render/cronjob_test.go +++ b/internal/render/cronjob_test.go @@ -18,5 +18,5 @@ func TestCronJobRender(t *testing.T) { require.NoError(t, c.Render(load(t, "cj"), "", &r)) assert.Equal(t, "default/hello", r.ID) - assert.Equal(t, model1.Fields{"default", "hello", "0", "*/1 * * * *", "false", "0"}, r.Fields[:6]) + assert.Equal(t, model1.Fields{"default", "hello", "n/a", "*/1 * * * *", "false", "0"}, r.Fields[:6]) } diff --git a/internal/render/dp_test.go b/internal/render/dp_test.go index 52148446..61cb8b26 100644 --- a/internal/render/dp_test.go +++ b/internal/render/dp_test.go @@ -18,7 +18,7 @@ func TestDpRender(t *testing.T) { require.NoError(t, c.Render(load(t, "dp"), "", &r)) assert.Equal(t, "icx/icx-db", r.ID) - assert.Equal(t, model1.Fields{"icx", "icx-db", "0", "1/1", "1", "1"}, r.Fields[:6]) + assert.Equal(t, model1.Fields{"icx", "icx-db", "n/a", "1/1", "1", "1"}, r.Fields[:6]) } func BenchmarkDpRender(b *testing.B) { diff --git a/internal/render/ds_test.go b/internal/render/ds_test.go index 2f16869f..7f8fb5f0 100644 --- a/internal/render/ds_test.go +++ b/internal/render/ds_test.go @@ -18,5 +18,5 @@ func TestDaemonSetRender(t *testing.T) { require.NoError(t, c.Render(load(t, "ds"), "", &r)) assert.Equal(t, "kube-system/fluentd-gcp-v3.2.0", r.ID) - assert.Equal(t, model1.Fields{"kube-system", "fluentd-gcp-v3.2.0", "0", "2", "2", "2", "2", "2"}, r.Fields[:8]) + assert.Equal(t, model1.Fields{"kube-system", "fluentd-gcp-v3.2.0", "n/a", "2", "2", "2", "2", "2"}, r.Fields[:8]) } diff --git a/internal/render/helpers.go b/internal/render/helpers.go index 48ebcf82..dec256ce 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -35,13 +35,14 @@ func ExtractImages(spec *v1.PodSpec) []string { } func computeVulScore(ns string, lbls map[string]string, spec *v1.PodSpec) string { - if vul.ImgScanner == nil || vul.ImgScanner.ShouldExcludes(ns, lbls) { - return "0" + if vul.ImgScanner == nil || !vul.ImgScanner.IsInitialized() || vul.ImgScanner.ShouldExcludes(ns, lbls) { + return NAValue } ii := ExtractImages(spec) vul.ImgScanner.Enqueue(context.Background(), ii...) + sc := vul.ImgScanner.Score(ii...) - return vul.ImgScanner.Score(ii...) + return sc } func runesToNum(rr []rune) int64 { diff --git a/internal/render/job_test.go b/internal/render/job_test.go index fc2c408f..5b9783c8 100644 --- a/internal/render/job_test.go +++ b/internal/render/job_test.go @@ -18,5 +18,5 @@ func TestJobRender(t *testing.T) { require.NoError(t, c.Render(load(t, "job"), "", &r)) assert.Equal(t, "default/hello-1567179180", r.ID) - assert.Equal(t, model1.Fields{"default", "hello-1567179180", "0", "1/1", "8s", "controller-uid=7473e6d0-cb3b-11e9-990f-42010a800218", "c1", "blang/busybox-bash"}, r.Fields[:8]) + assert.Equal(t, model1.Fields{"default", "hello-1567179180", "n/a", "1/1", "8s", "controller-uid=7473e6d0-cb3b-11e9-990f-42010a800218", "c1", "blang/busybox-bash"}, r.Fields[:8]) } diff --git a/internal/render/node.go b/internal/render/node.go index 5636ed15..a75ef57f 100644 --- a/internal/render/node.go +++ b/internal/render/node.go @@ -13,10 +13,12 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/slogs" "github.com/derailed/tview" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -48,6 +50,8 @@ var defaultNOHeader = model1.Header{ model1.HeaderColumn{Name: "%MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, model1.HeaderColumn{Name: "GPU/A", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, model1.HeaderColumn{Name: "GPU/C", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, + model1.HeaderColumn{Name: "SH-GPU/A", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, + model1.HeaderColumn{Name: "SH-GPU/C", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}}, model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}}, model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}}, @@ -130,6 +134,8 @@ func (n Node) defaultRow(nwm *NodeWithMetrics, r *model1.Row) error { client.ToPercentageStr(c.mem, a.mem), toMu(a.gpu), toMu(c.gpu), + toMu(a.gpuShared), + toMu(c.gpuShared), mapToStr(no.Labels), AsStatus(n.diagnose(statuses)), ToAge(no.GetCreationTimestamp()), @@ -203,8 +209,10 @@ func (n *NodeWithMetrics) DeepCopyObject() runtime.Object { } type metric struct { - cpu, gpu, mem int64 - lcpu, lgpu, lmem int64 + cpu, mem int64 + lcpu, lmem int64 + gpu, gpuShared int64 + lgpu int64 } func gatherNodeMX(no *v1.Node, mx *mv1beta1.NodeMetrics) (c, a metric) { @@ -215,8 +223,38 @@ func gatherNodeMX(no *v1.Node, mx *mv1beta1.NodeMetrics) (c, a metric) { c.mem = mx.Usage.Memory().Value() } - a.gpu = extractGPU(no.Status.Allocatable).Value() - c.gpu = extractGPU(no.Status.Capacity).Value() + gpu, gpuShared := extractNodeGPU(no.Status.Allocatable) + if gpu != nil { + a.gpu = gpu.Value() + } + if gpuShared != nil { + a.gpuShared = gpuShared.Value() + } + gpu, gpuShared = extractNodeGPU(no.Status.Capacity) + if gpu != nil { + c.gpu = gpu.Value() + } + if gpuShared != nil { + c.gpuShared = gpuShared.Value() + } + + return +} + +func extractNodeGPU(rl v1.ResourceList) (main, shared *resource.Quantity) { + mm := make(map[string]*resource.Quantity, len(config.KnownGPUVendors)) + for _, v := range config.KnownGPUVendors { + if q, ok := rl[v1.ResourceName(v)]; ok { + mm[v] = &q + } + } + for k, v := range mm { + if strings.HasSuffix(k, "shared") { + shared = v + } else { + main = v + } + } return } diff --git a/internal/render/node_int_test.go b/internal/render/node_int_test.go index dab462cd..d100723d 100644 --- a/internal/render/node_int_test.go +++ b/internal/render/node_int_test.go @@ -10,6 +10,70 @@ import ( mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) +func Test_extractNodeGPU(t *testing.T) { + uu := map[string]struct { + rl v1.ResourceList + main *resource.Quantity + shared *resource.Quantity + }{ + "empty": {}, + + "nvidia": { + rl: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + v1.ResourceMemory: resource.MustParse("4Gi"), + v1.ResourceName("nvidia.com/gpu"): resource.MustParse("2"), + }, + main: makeQ(t, "2"), + }, + + "nvidia-shared": { + rl: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + v1.ResourceMemory: resource.MustParse("4Gi"), + v1.ResourceName("nvidia.com/gpu.shared"): resource.MustParse("2"), + }, + shared: makeQ(t, "2"), + }, + + "nvidia-both": { + rl: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + v1.ResourceMemory: resource.MustParse("4Gi"), + v1.ResourceName("nvidia.com/gpu.shared"): resource.MustParse("2"), + v1.ResourceName("nvidia.com/gpu"): resource.MustParse("5"), + }, + main: makeQ(t, "5"), + shared: makeQ(t, "2"), + }, + + "intel": { + rl: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + v1.ResourceMemory: resource.MustParse("4Gi"), + v1.ResourceName("gpu.intel.com/i915"): resource.MustParse("5"), + }, + main: makeQ(t, "5"), + }, + + "unknown-vendor": { + rl: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + v1.ResourceMemory: resource.MustParse("4Gi"), + v1.ResourceName("bozo/gpu"): resource.MustParse("2"), + }, + }, + } + + for k, u := range uu { + t.Run(k, func(t *testing.T) { + m, s := extractNodeGPU(u.rl) + assert.Equal(t, u.main, m) + assert.Equal(t, u.shared, s) + }) + } +} + func Test_gatherNodeMX(t *testing.T) { uu := map[string]struct { node v1.Node @@ -125,3 +189,12 @@ func Test_gatherNodeMX(t *testing.T) { }) } } + +func makeQ(t *testing.T, v string) *resource.Quantity { + q, err := resource.ParseQuantity(v) + if err != nil { + t.Fatal(err) + } + + return &q +} diff --git a/internal/render/pod_test.go b/internal/render/pod_test.go index efd11add..a8aa560d 100644 --- a/internal/render/pod_test.go +++ b/internal/render/pod_test.go @@ -164,7 +164,7 @@ func TestPodRender(t *testing.T) { require.NoError(t, err) assert.Equal(t, "default/nginx", r.ID) - e := model1.Fields{"default", "nginx", "0", "●", "1/1", "Running", "0", "", "100", "100:0", "100", "n/a", "50", "70:170", "71", "29", "0:0", "172.17.0.6", "minikube", "default", ""} + e := model1.Fields{"default", "nginx", "n/a", "●", "1/1", "Running", "0", "", "100", "100:0", "100", "n/a", "50", "70:170", "71", "29", "0:0", "172.17.0.6", "minikube", "default", ""} assert.Equal(t, e, r.Fields[:21]) } @@ -195,7 +195,7 @@ func TestPodInitRender(t *testing.T) { require.NoError(t, err) assert.Equal(t, "default/nginx", r.ID) - e := model1.Fields{"default", "nginx", "0", "●", "1/1", "Init:0/1", "0", "", "10", "100:0", "10", "n/a", "10", "70:170", "14", "5", "0:0", "172.17.0.6", "minikube", "default", ""} + e := model1.Fields{"default", "nginx", "n/a", "●", "1/1", "Init:0/1", "0", "", "10", "100:0", "10", "n/a", "10", "70:170", "14", "5", "0:0", "172.17.0.6", "minikube", "default", ""} assert.Equal(t, e, r.Fields[:21]) } @@ -211,7 +211,7 @@ func TestPodSidecarRender(t *testing.T) { require.NoError(t, err) assert.Equal(t, "default/sleep", r.ID) - e := model1.Fields{"default", "sleep", "0", "●", "2/2", "Running", "0", "", "100", "50:250", "200", "40", "40", "50:80", "80", "50", "0:0", "10.244.0.8", "kind-control-plane", "default", ""} + e := model1.Fields{"default", "sleep", "n/a", "●", "2/2", "Running", "0", "", "100", "50:250", "200", "40", "40", "50:80", "80", "50", "0:0", "10.244.0.8", "kind-control-plane", "default", ""} assert.Equal(t, e, r.Fields[:21]) } diff --git a/internal/render/rs_test.go b/internal/render/rs_test.go index 29fc0563..b95dbff6 100644 --- a/internal/render/rs_test.go +++ b/internal/render/rs_test.go @@ -18,5 +18,5 @@ func TestReplicaSetRender(t *testing.T) { require.NoError(t, c.Render(load(t, "rs"), "", &r)) assert.Equal(t, "icx/icx-db-7d4b578979", r.ID) - assert.Equal(t, model1.Fields{"icx", "icx-db-7d4b578979", "0", "1", "1", "1"}, r.Fields[:6]) + assert.Equal(t, model1.Fields{"icx", "icx-db-7d4b578979", "n/a", "1", "1", "1"}, r.Fields[:6]) } diff --git a/internal/render/sts_test.go b/internal/render/sts_test.go index 0caaf850..9d8f23ec 100644 --- a/internal/render/sts_test.go +++ b/internal/render/sts_test.go @@ -18,5 +18,5 @@ func TestStatefulSetRender(t *testing.T) { require.NoError(t, c.Render(load(t, "sts"), "", &r)) assert.Equal(t, "default/nginx-sts", r.ID) - assert.Equal(t, model1.Fields{"default", "nginx-sts", "0", "4/4", "app=nginx-sts", "nginx-sts", "nginx", "k8s.gcr.io/nginx-slim:0.8", "app=nginx-sts", ""}, r.Fields[:len(r.Fields)-1]) + assert.Equal(t, model1.Fields{"default", "nginx-sts", "n/a", "4/4", "app=nginx-sts", "nginx-sts", "nginx", "k8s.gcr.io/nginx-slim:0.8", "app=nginx-sts", ""}, r.Fields[:len(r.Fields)-1]) } diff --git a/internal/slogs/keys.go b/internal/slogs/keys.go index f36ba0de..4dac9ff9 100644 --- a/internal/slogs/keys.go +++ b/internal/slogs/keys.go @@ -81,6 +81,7 @@ const ( // Context tracks a context logger key. Context = "context" + // Cluster tracks a cluster logger key. Cluster = "cluster" diff --git a/internal/ui/app_test.go b/internal/ui/app_test.go index eb1934e8..c09701d9 100644 --- a/internal/ui/app_test.go +++ b/internal/ui/app_test.go @@ -14,7 +14,7 @@ import ( func TestAppGetCmd(t *testing.T) { a := ui.NewApp(mock.NewMockConfig(t), "") a.Init() - a.CmdBuff().SetText("blee", "") + a.CmdBuff().SetText("blee", "", true) assert.Equal(t, "blee", a.GetCmd()) } @@ -22,7 +22,7 @@ func TestAppGetCmd(t *testing.T) { func TestAppInCmdMode(t *testing.T) { a := ui.NewApp(mock.NewMockConfig(t), "") a.Init() - a.CmdBuff().SetText("blee", "") + a.CmdBuff().SetText("blee", "", true) assert.False(t, a.InCmdMode()) a.CmdBuff().SetActive(false) @@ -32,7 +32,7 @@ func TestAppInCmdMode(t *testing.T) { func TestAppResetCmd(t *testing.T) { a := ui.NewApp(mock.NewMockConfig(t), "") a.Init() - a.CmdBuff().SetText("blee", "") + a.CmdBuff().SetText("blee", "", true) a.ResetCmd() @@ -46,7 +46,7 @@ func TestAppHasCmd(t *testing.T) { a.ActivateCmd(true) assert.False(t, a.HasCmd()) - a.CmdBuff().SetText("blee", "") + a.CmdBuff().SetText("blee", "", true) assert.True(t, a.InCmdMode()) } diff --git a/internal/ui/crumbs_test.go b/internal/ui/crumbs_test.go index 0bb84b02..dd2179b8 100644 --- a/internal/ui/crumbs_test.go +++ b/internal/ui/crumbs_test.go @@ -52,13 +52,13 @@ func (c) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { return ni func (c) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { return nil } -func (c) SetRect(int, int, int, int) {} -func (c) GetRect() (a, b, c, d int) { return 0, 0, 0, 0 } -func (c c) GetFocusable() tview.Focusable { return c } -func (c) Focus(func(tview.Primitive)) {} -func (c) Blur() {} -func (c) Start() {} -func (c) Stop() {} -func (c) Init(context.Context) error { return nil } -func (c) SetFilter(string) {} -func (c) SetLabelSelector(labels.Selector) {} +func (c) SetRect(int, int, int, int) {} +func (c) GetRect() (a, b, c, d int) { return 0, 0, 0, 0 } +func (c c) GetFocusable() tview.Focusable { return c } +func (c) Focus(func(tview.Primitive)) {} +func (c) Blur() {} +func (c) Start() {} +func (c) Stop() {} +func (c) Init(context.Context) error { return nil } +func (c) SetFilter(string, bool) {} +func (c) SetLabelSelector(labels.Selector, bool) {} diff --git a/internal/ui/prompt.go b/internal/ui/prompt.go index 4b2eac3c..ed6def24 100644 --- a/internal/ui/prompt.go +++ b/internal/ui/prompt.go @@ -41,7 +41,7 @@ type Suggester interface { // PromptModel represents a prompt buffer. type PromptModel interface { // SetText sets the model text. - SetText(txt, sug string) + SetText(txt, sug string, clear bool) // GetText returns the current text. GetText() string @@ -159,7 +159,7 @@ func (p *Prompt) keyboard(evt *tcell.EventKey) *tcell.EventKey { p.model.SetActive(false) case tcell.KeyEnter, tcell.KeyCtrlE: - p.model.SetText(p.model.GetText(), "") + p.model.SetText(p.model.GetText(), "", true) p.model.SetActive(false) case tcell.KeyCtrlW, tcell.KeyCtrlU: @@ -167,17 +167,17 @@ func (p *Prompt) keyboard(evt *tcell.EventKey) *tcell.EventKey { case tcell.KeyUp: if s, ok := m.NextSuggestion(); ok { - p.model.SetText(p.model.GetText(), s) + p.model.SetText(p.model.GetText(), s, true) } case tcell.KeyDown: if s, ok := m.PrevSuggestion(); ok { - p.model.SetText(p.model.GetText(), s) + p.model.SetText(p.model.GetText(), s, true) } case tcell.KeyTab, tcell.KeyRight, tcell.KeyCtrlF: if s, ok := m.CurrentSuggestion(); ok { - p.model.SetText(p.model.GetText()+s, "") + p.model.SetText(p.model.GetText()+s, "", true) m.ClearSuggestions() } } diff --git a/internal/ui/prompt_test.go b/internal/ui/prompt_test.go index 7f94d51e..57a1825b 100644 --- a/internal/ui/prompt_test.go +++ b/internal/ui/prompt_test.go @@ -68,7 +68,7 @@ func TestCmdUpdate(t *testing.T) { v.SetModel(m) m.AddListener(v) - m.SetText("blee", "") + m.SetText("blee", "", true) m.Add('!') assert.Equal(t, "\x00\x00 [::b]blee!\n", v.GetText(false)) diff --git a/internal/ui/table.go b/internal/ui/table.go index 73929df3..e2932225 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -560,7 +560,7 @@ func (t *Table) styleTitle() string { buff := t.cmdBuff.GetText() if internal.IsLabelSelector(buff) { - if sel, err := TrimLabelSelector(buff); err == nil { + if sel, err := ExtractLabelSelector(buff); err == nil { buff = render.Truncate(sel.String(), maxTruncate) } } else if l := t.GetModel().GetLabelSelector(); l != nil && !l.Empty() { diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index e854aafe..8722b75a 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -58,8 +58,8 @@ func TrimCell(tv *SelectTable, row, col int) string { return strings.TrimSpace(c.Text) } -// TrimLabelSelector extracts label query. -func TrimLabelSelector(s string) (labels.Selector, error) { +// ExtractLabelSelector extracts label query. +func ExtractLabelSelector(s string) (labels.Selector, error) { selStr := s if strings.Index(s, "-l") == 0 { selStr = strings.TrimSpace(s[2:]) diff --git a/internal/ui/table_helper_test.go b/internal/ui/table_helper_test.go index 48c8c274..ea4c95cb 100644 --- a/internal/ui/table_helper_test.go +++ b/internal/ui/table_helper_test.go @@ -34,7 +34,7 @@ func TestTruncate(t *testing.T) { } } -func TestTrimLabelSelector(t *testing.T) { +func TestExtractLabelSelector(t *testing.T) { sel, _ := labels.Parse("app=fred,env=blee") uu := map[string]struct { sel string @@ -55,7 +55,7 @@ func TestTrimLabelSelector(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - sel, err := TrimLabelSelector(u.sel) + sel, err := ExtractLabelSelector(u.sel) assert.Equal(t, u.err, err) assert.Equal(t, u.e, sel) }) diff --git a/internal/view/actions.go b/internal/view/actions.go index 613070cc..0c58ee45 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "log/slog" + "slices" "strings" "github.com/derailed/k9s/internal/config" @@ -36,21 +37,11 @@ type Runner interface { } func hasAll(scopes []string) bool { - for _, s := range scopes { - if s == AllScopes { - return true - } - } - return false + return slices.Contains(scopes, AllScopes) } func includes(aliases []string, s string) bool { - for _, a := range aliases { - if a == s { - return true - } - } - return false + return slices.Contains(aliases, s) } func inScope(scopes []string, aliases sets.Set[string]) bool { diff --git a/internal/view/app.go b/internal/view/app.go index cb1cbadb..1d850788 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -11,7 +11,6 @@ import ( "maps" "os" "os/signal" - "runtime" "sort" "strings" "sync/atomic" @@ -241,9 +240,8 @@ func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey { func (a *App) bindKeys() { a.AddActions(ui.NewKeyActionsFromMap(ui.KeyMap{ - ui.KeyShift9: ui.NewSharedKeyAction("DumpGOR", a.dumpGOR, false), tcell.KeyCtrlE: ui.NewSharedKeyAction("ToggleHeader", a.toggleHeaderCmd, false), - tcell.KeyCtrlG: ui.NewSharedKeyAction("toggleCrumbs", a.toggleCrumbsCmd, false), + tcell.KeyCtrlG: ui.NewSharedKeyAction("ToggleCrumbs", a.toggleCrumbsCmd, false), ui.KeyHelp: ui.NewSharedKeyAction("Help", a.helpCmd, false), ui.KeyLeftBracket: ui.NewSharedKeyAction("Go Back", a.previousCommand, false), ui.KeyRightBracket: ui.NewSharedKeyAction("Go Forward", a.nextCommand, false), @@ -254,15 +252,6 @@ func (a *App) bindKeys() { })) } -func (*App) dumpGOR(evt *tcell.EventKey) *tcell.EventKey { - slog.Debug("GOR", slogs.GOR, runtime.NumGoroutine()) - bb := make([]byte, 5_000_000) - runtime.Stack(bb, true) - slog.Debug("GOR stack", slogs.Stack, string(bb)) - - return evt -} - // ActiveView returns the currently active view. func (a *App) ActiveView() model.Component { return a.Content.GetPrimitive("main").(model.Component) diff --git a/internal/view/app_test.go b/internal/view/app_test.go index 24d8d7fa..8f78114c 100644 --- a/internal/view/app_test.go +++ b/internal/view/app_test.go @@ -15,5 +15,5 @@ func TestAppNew(t *testing.T) { a := view.NewApp(mock.NewMockConfig(t)) _ = a.Init("blee", 10) - assert.Equal(t, 15, a.GetActions().Len()) + assert.Equal(t, 14, a.GetActions().Len()) } diff --git a/internal/view/browser.go b/internal/view/browser.go index 237b15ef..90d8dd02 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -197,13 +197,13 @@ func (b *Browser) Stop() { b.Table.Stop() } -func (b *Browser) SetFilter(s string) { - b.CmdBuff().SetText(s, "") +func (b *Browser) SetFilter(s string, wipe bool) { + b.CmdBuff().SetText(s, "", wipe) } -func (b *Browser) SetLabelSelector(sel labels.Selector) { +func (b *Browser) SetLabelSelector(sel labels.Selector, wipe bool) { if sel != nil { - b.CmdBuff().SetText(sel.String(), "") + b.CmdBuff().SetText(sel.String(), "", wipe) } b.GetModel().SetLabelSelector(sel) } @@ -214,7 +214,7 @@ func (*Browser) BufferChanged(_, _ string) {} // BufferCompleted indicates input was accepted. func (b *Browser) BufferCompleted(text, _ string) { if internal.IsLabelSelector(text) { - if sel, err := ui.TrimLabelSelector(text); err == nil { + if sel, err := ui.ExtractLabelSelector(text); err == nil { b.GetModel().SetLabelSelector(sel) } } else { @@ -575,7 +575,7 @@ func (b *Browser) defaultContext() context.Context { ctx = context.WithValue(ctx, internal.KeyGVR, b.GVR()) ctx = context.WithValue(ctx, internal.KeyPath, b.Path) if internal.IsLabelSelector(b.CmdBuff().GetText()) { - if sel, err := ui.TrimLabelSelector(b.CmdBuff().GetText()); err == nil { + if sel, err := ui.ExtractLabelSelector(b.CmdBuff().GetText()); err == nil { ctx = context.WithValue(ctx, internal.KeyLabels, sel) } } diff --git a/internal/view/cmd/args.go b/internal/view/cmd/args.go index 3b2ffb5c..478eb50f 100644 --- a/internal/view/cmd/args.go +++ b/internal/view/cmd/args.go @@ -4,6 +4,8 @@ package cmd import ( + "maps" + "slices" "strings" ) @@ -46,10 +48,8 @@ func newArgs(p *Interpreter, aa []string) args { arguments[filterKey] = strings.ToLower(a[1:]) } - case strings.Contains(a, labelFlag): - if ll := ToLabels(a); len(ll) != 0 { - arguments[labelKey] = strings.ToLower(a) - } + case isLabelArg(a): + arguments[labelKey] = strings.ToLower(a) case strings.Index(a, contextFlag) == 0: arguments[contextKey] = a[1:] @@ -80,6 +80,23 @@ func newArgs(p *Interpreter, aa []string) args { return arguments } +func (a args) String() string { + ss := make([]string, 0, len(a)) + kk := maps.Keys(a) + for _, k := range slices.Sorted(kk) { + v := a[k] + switch k { + case filterKey: + v = filterFlag + v + case contextKey: + v = contextFlag + v + } + ss = append(ss, v) + } + + return strings.Join(ss, " ") +} + func (a args) hasFilters() bool { _, fok := a[filterKey] _, zok := a[fuzzyKey] @@ -87,3 +104,13 @@ func (a args) hasFilters() bool { return fok || zok || lok } + +func isLabelArg(arg string) bool { + for _, flag := range labelFlags { + if strings.Contains(arg, flag) { + return true + } + } + + return false +} diff --git a/internal/view/cmd/args_test.go b/internal/view/cmd/args_test.go index 69acaad2..53e360fe 100644 --- a/internal/view/cmd/args_test.go +++ b/internal/view/cmd/args_test.go @@ -71,7 +71,7 @@ func TestFlagsNew(t *testing.T) { "label-toast": { i: NewInterpreter("po"), aa: []string{"="}, - ll: make(args), + ll: args{labelKey: "="}, }, "multi-labels": { diff --git a/internal/view/cmd/helpers.go b/internal/view/cmd/helpers.go index 2c0065ec..fbd56359 100644 --- a/internal/view/cmd/helpers.go +++ b/internal/view/cmd/helpers.go @@ -10,17 +10,18 @@ import ( "github.com/derailed/k9s/internal/client" ) +// ToLabels converts a string into a map of labels. func ToLabels(s string) map[string]string { var ( ll = strings.Split(s, ",") lbls = make(map[string]string, len(ll)) ) for _, l := range ll { - kv := strings.Split(l, "=") - if len(kv) < 2 || kv[0] == "" || kv[1] == "" { + if k, v, ok := splitKv(l); ok { + lbls[k] = v + } else { continue } - lbls[kv[0]] = kv[1] } if len(lbls) == 0 { return nil @@ -29,6 +30,28 @@ func ToLabels(s string) map[string]string { return lbls } +func splitKv(s string) (k, v string, ok bool) { + switch { + case strings.Contains(s, labelFlagNotEq): + kv := strings.SplitN(s, labelFlagNotEq, 2) + if len(kv) == 2 && kv[0] != "" && kv[1] != "" { + return strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1]), true + } + case strings.Contains(s, labelFlagEqs): + kv := strings.SplitN(s, labelFlagEqs, 2) + if len(kv) == 2 && kv[0] != "" && kv[1] != "" { + return strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1]), true + } + case strings.Contains(s, labelFlagEq): + kv := strings.SplitN(s, labelFlagEq, 2) + if len(kv) == 2 && kv[0] != "" && kv[1] != "" { + return strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1]), true + } + } + + return "", "", false +} + // ShouldAddSuggest checks if a suggestion match the given command. func ShouldAddSuggest(command, suggest string) (string, bool) { if command != suggest && strings.HasPrefix(suggest, command) { diff --git a/internal/view/cmd/interpreter.go b/internal/view/cmd/interpreter.go index a95cc4f6..51be96b6 100644 --- a/internal/view/cmd/interpreter.go +++ b/internal/view/cmd/interpreter.go @@ -4,9 +4,12 @@ package cmd import ( + "log/slog" "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/slogs" + "k8s.io/apimachinery/pkg/labels" ) // Interpreter tracks user prompt input. @@ -43,13 +46,59 @@ func (c *Interpreter) SwitchNS(ns string) { } } +func (c *Interpreter) Merge(p *Interpreter) { + if p == nil { + return + } + c.cmd = p.cmd + for k, v := range p.args { + // if _, ok := c.args[k]; !ok { + c.args[k] = v + // } + } + c.line = c.cmd + " " + c.args.String() +} + func (c *Interpreter) grok() { ff := strings.Fields(c.line) if len(ff) == 0 { return } c.cmd = strings.ToLower(ff[0]) - c.args = newArgs(c, ff[1:]) + + var lbls string + line := strings.TrimSpace(strings.Replace(c.line, c.cmd, "", 1)) + if strings.Contains(line, "'") { + start, end, ok := quoteIndicies(line) + if ok { + lbls = line[start+1 : end] + line = strings.TrimSpace(strings.Replace(line, "'"+lbls+"'", "", 1)) + } else { + slog.Error("Unmatched single quote in command line", slogs.Line, c.line) + line = "" + } + } + ff = strings.Fields(line) + if lbls != "" { + ff = append(ff, lbls) + } + c.args = newArgs(c, ff) +} + +func quoteIndicies(s string) (start, end int, ok bool) { + start, end = -1, -1 + for i, r := range s { + if r == '\'' { + if start == -1 { + start = i + } else if end == -1 { + end = i + break + } + } + } + ok = start != -1 && end != -1 + return } // HasNS returns true if ns is present in prompt. @@ -244,8 +293,6 @@ func (c *Interpreter) HasContext() (string, bool) { } // LabelsArg return the labels map if any. -func (c *Interpreter) LabelsArg() (map[string]string, bool) { - ll, ok := c.args[labelKey] - - return ToLabels(ll), ok +func (c *Interpreter) LabelsSelector() (labels.Selector, error) { + return labels.Parse(c.args[labelKey]) } diff --git a/internal/view/cmd/interpreter_test.go b/internal/view/cmd/interpreter_test.go index 6fa6b532..65b7e391 100644 --- a/internal/view/cmd/interpreter_test.go +++ b/internal/view/cmd/interpreter_test.go @@ -4,6 +4,7 @@ package cmd_test import ( + "errors" "testing" "github.com/derailed/k9s/internal/view/cmd" @@ -219,34 +220,48 @@ func TestFilterCmd(t *testing.T) { func TestLabelCmd(t *testing.T) { uu := map[string]struct { cmd string - ok bool - labels map[string]string + err error + labels string }{ "empty": {}, + "plain": { cmd: "pod fred=blee", - ok: true, - labels: map[string]string{"fred": "blee"}, + labels: "fred=blee", }, + "multi": { cmd: "pod fred=blee,zorg=duh", - ok: true, - labels: map[string]string{"fred": "blee", "zorg": "duh"}, + labels: "fred=blee,zorg=duh", }, + + "complex-lbls": { + cmd: "pod 'fred in (blee,zorg),blee notin (zorg)'", + labels: "blee notin (zorg),fred in (blee,zorg)", + }, + + "no-lbls": { + cmd: "pod ns-1", + }, + "multi-ns": { cmd: "pod fred=blee,zorg=duh ns1", - ok: true, - labels: map[string]string{"fred": "blee", "zorg": "duh"}, + labels: "fred=blee,zorg=duh", }, + "l-arg-spaced": { cmd: "pod fred=blee ", - ok: true, - labels: map[string]string{"fred": "blee"}, + labels: "fred=blee", }, + "l-arg-caps": { cmd: "POD FRED=BLEE ", - ok: true, - labels: map[string]string{"fred": "blee"}, + labels: "fred=blee", + }, + + "toast-labels": { + cmd: "pod =blee", + err: errors.New("found '=', expected: !, identifier, or 'end of string'"), }, } @@ -254,10 +269,10 @@ func TestLabelCmd(t *testing.T) { u := uu[k] t.Run(k, func(t *testing.T) { p := cmd.NewInterpreter(u.cmd) - ll, ok := p.LabelsArg() - assert.Equal(t, u.ok, ok) - if u.ok { - assert.Equal(t, u.labels, ll) + ll, err := p.LabelsSelector() + assert.Equal(t, u.err, err) + if err == nil { + assert.Equal(t, u.labels, ll.String()) } }) } @@ -595,3 +610,49 @@ func TestArgs(t *testing.T) { }) } } + +func Test_grokLabels(t *testing.T) { + uu := map[string]struct { + cmd string + err error + lbls string + }{ + "empty": {}, + + "no-labels": { + cmd: "po @fred", + }, + + "plain-label": { + cmd: "po a=b,b=c @fred", + lbls: "a=b,b=c", + }, + + "label-quotes": { + cmd: "po 'a=b,b=c' @fred", + lbls: "a=b,b=c", + }, + + "partial-quotes-label": { + cmd: "po 'a=b @fred", + lbls: "", + }, + + "complex": { + cmd: "po 'a in (b,c),b notin (c,z)' fred'", + lbls: "a in (b,c),b notin (c,z)", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + sel, err := p.LabelsSelector() + assert.Equal(t, u.err, err) + if err == nil { + assert.Equal(t, u.lbls, sel.String()) + } + }) + } +} diff --git a/internal/view/cmd/types.go b/internal/view/cmd/types.go index 522f6c68..052e2c92 100644 --- a/internal/view/cmd/types.go +++ b/internal/view/cmd/types.go @@ -10,16 +10,29 @@ import ( ) const ( - cowCmd = "cow" - canCmd = "can" - nsFlag = "-n" - filterFlag = "/" - labelFlag = "=" + cowCmd = "cow" + canCmd = "can" + nsFlag = "-n" + filterFlag = "/" + labelFlagEq = "=" + labelFlagEqs = "==" + labelFlagNotEq = "!=" + labelFlagIn = "in" + labelFlagNotin = "notin" + labelFlagQuote = "'" + label fuzzyFlag = "-f" contextFlag = "@" ) var ( + labelFlags = []string{ + labelFlagEq, + labelFlagEqs, + labelFlagNotEq, + labelFlagIn, + labelFlagNotin, + } rbacRX = regexp.MustCompile(`^can\s+([ugs]):\s*([\w-:]+)\s*$`) contextCmd = sets.New( diff --git a/internal/view/command.go b/internal/view/command.go index 4ff62b54..2cfbaaad 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -100,10 +100,13 @@ func (c *Command) contextCmd(p *cmd.Interpreter, pushCmd bool) error { return useContext(c.app, ct) } - gvr, v, err := c.viewMetaFor(p) + gvr, v, comd, err := c.viewMetaFor(p) if err != nil { return err } + if comd != nil { + p = comd + } return c.exec(p, gvr, c.componentFor(gvr, ct, v), true, pushCmd) } @@ -126,7 +129,7 @@ func (c *Command) aliasCmd(p *cmd.Interpreter, pushCmd bool) error { filter, _ := p.FilterArg() v := NewAlias(client.AliGVR) - v.SetFilter(filter) + v.SetFilter(filter, true) return c.exec(p, client.AliGVR, v, false, pushCmd) } @@ -136,7 +139,7 @@ func (c *Command) xrayCmd(p *cmd.Interpreter, pushCmd bool) error { if !ok { return errors.New("invalid command. use `xray xxx`") } - gvr, _, ok := c.alias.AsGVR(arg) + gvr, ok := c.alias.Resolve(p) if !ok { return fmt.Errorf("invalid resource name: %q", arg) } @@ -154,7 +157,7 @@ func (c *Command) xrayCmd(p *cmd.Interpreter, pushCmd bool) error { return err } - return c.exec(p, client.NewGVR("xrays"), NewXray(gvr), true, pushCmd) + return c.exec(p, client.XGVR, NewXray(gvr), true, pushCmd) } // Run execs the command by showing associated display. @@ -162,10 +165,13 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack, pushCmd bool) if c.specialCmd(p, pushCmd) { return nil } - gvr, v, err := c.viewMetaFor(p) + gvr, v, comd, err := c.viewMetaFor(p) if err != nil { return err } + if comd != nil { + p.Merge(comd) + } if context, ok := p.HasContext(); ok { if context != c.app.Config.ActiveContextName() { @@ -206,16 +212,18 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack, pushCmd bool) } co := c.componentFor(gvr, fqn, v) - co.SetFilter("") - co.SetLabelSelector(labels.Everything()) + co.SetFilter("", true) + co.SetLabelSelector(labels.Everything(), true) if f, ok := p.FilterArg(); ok { - co.SetFilter(f) + co.SetFilter("/"+f, true) } if f, ok := p.FuzzyArg(); ok { - co.SetFilter("-f " + f) + co.SetFilter("-f "+f, true) } - if ss, ok := p.LabelsArg(); ok { - co.SetLabelSelector(labels.SelectorFromSet(ss)) + if sel, err := p.LabelsSelector(); err == nil { + co.SetLabelSelector(sel, false) + } else { + slog.Error("Unable to grok labels selector", slogs.Error, err) } return c.exec(p, gvr, co, clearStack, pushCmd) @@ -292,13 +300,10 @@ func (c *Command) specialCmd(p *cmd.Interpreter, pushCmd bool) bool { return true } -func (c *Command) viewMetaFor(p *cmd.Interpreter) (*client.GVR, *MetaViewer, error) { - gvr, exp, ok := c.alias.AsGVR(p.Cmd()) +func (c *Command) viewMetaFor(p *cmd.Interpreter) (*client.GVR, *MetaViewer, *cmd.Interpreter, error) { + gvr, ok := c.alias.Resolve(p) if !ok { - return client.NoGVR, nil, fmt.Errorf("`%s` command not found", p.Cmd()) - } - if exp != "" { - p.Amend(cmd.NewInterpreter(gvr.String() + " " + exp)) + return client.NoGVR, nil, nil, fmt.Errorf("`%s` command not found", p.Cmd()) } v := MetaViewer{ @@ -310,7 +315,7 @@ func (c *Command) viewMetaFor(p *cmd.Interpreter) (*client.GVR, *MetaViewer, err v = mv } - return gvr, &v, nil + return gvr, &v, p, nil } func (*Command) componentFor(gvr *client.GVR, fqn string, v *MetaViewer) ResourceViewer { diff --git a/internal/view/command_test.go b/internal/view/command_test.go index 0007bac4..5795cc67 100644 --- a/internal/view/command_test.go +++ b/internal/view/command_test.go @@ -15,6 +15,7 @@ func Test_viewMetaFor(t *testing.T) { uu := map[string]struct { cmd string gvr *client.GVR + p *cmd.Interpreter err error }{ "empty": { @@ -23,27 +24,37 @@ func Test_viewMetaFor(t *testing.T) { err: errors.New("`` command not found"), }, - "toast-cmd": { + "toast": { cmd: "v1/pd", gvr: client.PodGVR, err: errors.New("`v1/pd` command not found"), }, - "gvr-cmd": { + "gvr": { cmd: "v1/pods", gvr: client.PodGVR, + p: cmd.NewInterpreter("v1/pods"), err: errors.New("blah"), }, - "alias-cmd": { + "short-name": { cmd: "po", gvr: client.PodGVR, + p: cmd.NewInterpreter("v1/pods"), err: errors.New("blee"), }, - "full-cmd": { + "custom-alias": { cmd: "pdl", gvr: client.PodGVR, + p: cmd.NewInterpreter("v1/pods @fred app=blee default"), + err: errors.New("blee"), + }, + + "inception": { + cmd: "pdal blee", + gvr: client.PodGVR, + p: cmd.NewInterpreter("v1/pods @fred app=blee blee"), err: errors.New("blee"), }, } @@ -55,16 +66,18 @@ func Test_viewMetaFor(t *testing.T) { } c.alias.Define(client.PodGVR, "po", "pod", "pods", client.PodGVR.String()) c.alias.Define(client.NewGVR("pod default"), "pd") - c.alias.Define(client.NewGVR("pod default app=blee @fred"), "pdl") + c.alias.Define(client.NewGVR("pod @fred app=blee default"), "pdl") + c.alias.Define(client.NewGVR("pdl"), "pdal") for k, u := range uu { t.Run(k, func(t *testing.T) { p := cmd.NewInterpreter(u.cmd) - gvr, _, err := c.viewMetaFor(p) + gvr, _, acmd, err := c.viewMetaFor(p) if err != nil { assert.Equal(t, u.err.Error(), err.Error()) } else { assert.Equal(t, u.gvr, gvr) + assert.Equal(t, u.p, acmd) } }) } diff --git a/internal/view/details.go b/internal/view/details.go index ef1c79c1..f742337d 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -60,9 +60,9 @@ func NewDetails(app *App, title, subject, contentType string, searchable bool) * return &d } -func (*Details) SetCommand(*cmd.Interpreter) {} -func (*Details) SetFilter(string) {} -func (*Details) SetLabelSelector(labels.Selector) {} +func (*Details) SetCommand(*cmd.Interpreter) {} +func (*Details) SetFilter(string, bool) {} +func (*Details) SetLabelSelector(labels.Selector, bool) {} // Init initializes the viewer. func (d *Details) Init(_ context.Context) error { diff --git a/internal/view/dir.go b/internal/view/dir.go index 0fa328ef..80dc61bc 100644 --- a/internal/view/dir.go +++ b/internal/view/dir.go @@ -219,7 +219,7 @@ func (d *Dir) applyCmd(evt *tcell.EventKey) *tcell.EventKey { args = append(args, "apply") args = append(args, opts...) args = append(args, sel) - res, err := runKu(d.App(), &shellOpts{clear: false, args: args}) + res, err := runKu(context.Background(), d.App(), &shellOpts{clear: false, args: args}) if err != nil { res = "status:\n " + err.Error() + "\nmessage:\n" + fmtResults(res) } else { @@ -260,7 +260,7 @@ func (d *Dir) delCmd(evt *tcell.EventKey) *tcell.EventKey { args = append(args, "delete") args = append(args, opts...) args = append(args, sel) - res, err := runKu(d.App(), &shellOpts{clear: false, args: args}) + res, err := runKu(context.Background(), d.App(), &shellOpts{clear: false, args: args}) if err != nil { res = "status:\n " + err.Error() + "\nmessage:\n" + fmtResults(res) } else { diff --git a/internal/view/exec.go b/internal/view/exec.go index ee3f384f..60d79242 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -180,6 +180,7 @@ func execute(opts *shellOpts, statusChan chan<- string) error { } }() + var interrupted bool sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) go func(cancel context.CancelFunc) { @@ -191,6 +192,7 @@ func execute(opts *shellOpts, statusChan chan<- string) error { case <-ctx.Done(): slog.Debug("Signal context canceled!") } + interrupted = true }(cancel) cmds := make([]*exec.Cmd, 0, 1) @@ -224,8 +226,8 @@ func execute(opts *shellOpts, statusChan chan<- string) error { var o, e bytes.Buffer err := pipe(ctx, opts, statusChan, &o, &e, cmds...) - if err != nil { - slog.Error("Exec failed", + if err != nil && !interrupted { + slog.Error("Pipe Exec failed", slogs.Error, err, slogs.Command, cmds, ) @@ -235,7 +237,7 @@ func execute(opts *shellOpts, statusChan chan<- string) error { return nil } -func runKu(a *App, opts *shellOpts) (string, error) { +func runKu(ctx context.Context, a *App, opts *shellOpts) (string, error) { bin, err := exec.LookPath("kubectl") if errors.Is(err, exec.ErrDot) { slog.Error("Kubectl exec can not reside in current working directory", slogs.Error, err) @@ -261,10 +263,10 @@ func runKu(a *App, opts *shellOpts) (string, error) { } opts.binary, opts.background = bin, false - return oneShoot(opts) + return oneShoot(ctx, opts) } -func oneShoot(opts *shellOpts) (string, error) { +func oneShoot(ctx context.Context, opts *shellOpts) (string, error) { if opts.clear { clearScreen() } @@ -273,7 +275,7 @@ func oneShoot(opts *shellOpts) (string, error) { slogs.Bin, opts.binary, slogs.Args, strings.Join(opts.args, " "), ) - cmd := exec.Command(opts.binary, opts.args...) + cmd := exec.CommandContext(ctx, opts.binary, opts.args...) var err error buff := bytes.NewBufferString("") @@ -573,12 +575,21 @@ func pipe(_ context.Context, opts *shellOpts, statusChan chan<- string, w, e *by slog.Debug("Exec started") err := cmd.Run() - slog.Debug("Running exec done", slogs.Error, err) + var ex *exec.ExitError + // Check if exec failed from a signal + if errors.As(err, &ex) && !ex.Exited() { + return nil + } + slog.Debug("Command exec done", slogs.Error, err) if err == nil { statusChan <- fmt.Sprintf("Command completed successfully: %q", cmd.String()) } close(statusChan) + if err != nil { + err = fmt.Errorf("command failed. Check logs: %w", err) + } + return err } diff --git a/internal/view/help.go b/internal/view/help.go index 77cda9ba..3f785016 100644 --- a/internal/view/help.go +++ b/internal/view/help.go @@ -45,9 +45,9 @@ func NewHelp(app *App) *Help { } } -func (*Help) SetCommand(*cmd.Interpreter) {} -func (*Help) SetFilter(string) {} -func (*Help) SetLabelSelector(labels.Selector) {} +func (*Help) SetCommand(*cmd.Interpreter) {} +func (*Help) SetFilter(string, bool) {} +func (*Help) SetLabelSelector(labels.Selector, bool) {} // Init initializes the component. func (h *Help) Init(ctx context.Context) error { diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 6fc7f13e..34fd163d 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -145,7 +145,7 @@ func showReplicasets(app *App, path string, labelSel labels.Selector, fieldSel s ctx = context.WithValue(ctx, internal.KeyPath, path) return context.WithValue(ctx, internal.KeyFields, fieldSel) }) - v.SetLabelSelector(labelSel) + v.SetLabelSelector(labelSel, true) ns, _ := client.Namespaced(path) if err := app.Config.SetActiveNamespace(ns); err != nil { @@ -159,7 +159,7 @@ func showReplicasets(app *App, path string, labelSel labels.Selector, fieldSel s func showPods(app *App, path string, labelSel labels.Selector, fieldSel string) { v := NewPod(client.PodGVR) v.SetContextFn(podCtx(app, path, fieldSel)) - v.SetLabelSelector(labelSel) + v.SetLabelSelector(labelSel, true) ns, _ := client.Namespaced(path) if err := app.Config.SetActiveNamespace(ns); err != nil { diff --git a/internal/view/live_view.go b/internal/view/live_view.go index e34347dc..8a738fb4 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -63,9 +63,9 @@ func NewLiveView(app *App, title string, m model.ResourceViewer) *LiveView { return &v } -func (*LiveView) SetCommand(*cmd.Interpreter) {} -func (*LiveView) SetFilter(string) {} -func (*LiveView) SetLabelSelector(labels.Selector) {} +func (*LiveView) SetCommand(*cmd.Interpreter) {} +func (*LiveView) SetFilter(string, bool) {} +func (*LiveView) SetLabelSelector(labels.Selector, bool) {} // Init initializes the viewer. func (v *LiveView) Init(_ context.Context) error { diff --git a/internal/view/log.go b/internal/view/log.go index fc5b254f..fc0c64bb 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -65,9 +65,9 @@ func NewLog(gvr *client.GVR, opts *dao.LogOptions) *Log { return &l } -func (*Log) SetCommand(*cmd.Interpreter) {} -func (*Log) SetFilter(string) {} -func (*Log) SetLabelSelector(labels.Selector) {} +func (*Log) SetCommand(*cmd.Interpreter) {} +func (*Log) SetFilter(string, bool) {} +func (*Log) SetLabelSelector(labels.Selector, bool) {} // Init initializes the viewer. func (l *Log) Init(ctx context.Context) (err error) { diff --git a/internal/view/pf_extender.go b/internal/view/pf_extender.go index 9673427c..9ea6c011 100644 --- a/internal/view/pf_extender.go +++ b/internal/view/pf_extender.go @@ -146,7 +146,7 @@ func runForward(v ResourceViewer, pf watch.Forwarder, f *portforward.PortForward } func startFwdCB(v ResourceViewer, path string, pts port.PortTunnels) error { - if err := pts.CheckAvailable(); err != nil { + if err := pts.CheckAvailable(context.Background()); err != nil { return err } diff --git a/internal/view/picker.go b/internal/view/picker.go index 5ff7006d..9f53db31 100644 --- a/internal/view/picker.go +++ b/internal/view/picker.go @@ -5,6 +5,7 @@ package view import ( "context" + "fmt" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/ui" @@ -29,9 +30,9 @@ func NewPicker() *Picker { } } -func (*Picker) SetCommand(*cmd.Interpreter) {} -func (*Picker) SetFilter(string) {} -func (*Picker) SetLabelSelector(labels.Selector) {} +func (*Picker) SetCommand(*cmd.Interpreter) {} +func (*Picker) SetFilter(string, bool) {} +func (*Picker) SetLabelSelector(labels.Selector, bool) {} // Init initializes the view. func (p *Picker) Init(ctx context.Context) error { @@ -48,7 +49,7 @@ func (p *Picker) Init(ctx context.Context) error { p.ShowSecondaryText(false) p.SetShortcutColor(pickerView.ShortcutColor.Color()) p.SetSelectedBackgroundColor(pickerView.FocusColor.Color()) - p.SetTitle(" [aqua::b]Containers Picker ") + p.SetTitle(fmt.Sprintf(" [%s::b]Containers Picker ", app.Styles.Frame().Title.FgColor.String())) p.SetInputCapture(func(evt *tcell.EventKey) *tcell.EventKey { if a, ok := p.actions.Get(evt.Key()); ok { diff --git a/internal/view/pulse.go b/internal/view/pulse.go index 9b641b19..16811b3c 100644 --- a/internal/view/pulse.go +++ b/internal/view/pulse.go @@ -154,9 +154,9 @@ func (*Pulse) InCmdMode() bool { return false } -func (*Pulse) SetCommand(*cmd.Interpreter) {} -func (*Pulse) SetFilter(string) {} -func (*Pulse) SetLabelSelector(labels.Selector) {} +func (*Pulse) SetCommand(*cmd.Interpreter) {} +func (*Pulse) SetFilter(string, bool) {} +func (*Pulse) SetLabelSelector(labels.Selector, bool) {} // StylesChanged notifies the skin changed. func (p *Pulse) StylesChanged(s *config.Styles) { @@ -417,8 +417,8 @@ func (p *Pulse) enterCmd(*tcell.EventKey) *tcell.EventKey { p.Stop() res := client.NewGVR(s.ID()).R() - if res == "cpu" || res == "mem" { - res = p.app.Config.K9s.DefaultView + if res == "cpu" || res == "memory" { + res = client.PodGVR.String() } p.App().SetFocus(p.App().Main) p.App().gotoResource(res+" "+p.model.GetNamespace(), "", false, true) diff --git a/internal/view/table_int_test.go b/internal/view/table_int_test.go index 3f4f3497..49b12199 100644 --- a/internal/view/table_int_test.go +++ b/internal/view/table_int_test.go @@ -80,7 +80,7 @@ func TestTableViewFilter(t *testing.T) { v.Refresh() v.CmdBuff().SetActive(true) - v.CmdBuff().SetText("blee", "") + v.CmdBuff().SetText("blee", "", true) assert.Equal(t, 5, v.GetRowCount()) } diff --git a/internal/view/workload.go b/internal/view/workload.go index 8c2c18ef..6cda571c 100644 --- a/internal/view/workload.go +++ b/internal/view/workload.go @@ -112,7 +112,7 @@ func (w *Workload) defaultContext(gvr *client.GVR, fqn string) context.Context { ctx = context.WithValue(ctx, internal.KeyPath, fqn) } if internal.IsLabelSelector(w.GetTable().CmdBuff().GetText()) { - if sel, err := ui.TrimLabelSelector(w.GetTable().CmdBuff().GetText()); err == nil { + if sel, err := ui.ExtractLabelSelector(w.GetTable().CmdBuff().GetText()); err == nil { ctx = context.WithValue(ctx, internal.KeyLabels, sel) } } diff --git a/internal/view/xray.go b/internal/view/xray.go index 15ad0ffe..ab775477 100644 --- a/internal/view/xray.go +++ b/internal/view/xray.go @@ -56,9 +56,9 @@ func NewXray(gvr *client.GVR) ResourceViewer { } } -func (*Xray) SetCommand(*cmd.Interpreter) {} -func (*Xray) SetFilter(string) {} -func (*Xray) SetLabelSelector(labels.Selector) {} +func (*Xray) SetCommand(*cmd.Interpreter) {} +func (*Xray) SetFilter(string, bool) {} +func (*Xray) SetLabelSelector(labels.Selector, bool) {} // Init initializes the view. func (x *Xray) Init(ctx context.Context) error { @@ -611,7 +611,7 @@ func (x *Xray) defaultContext() context.Context { if x.CmdBuff().Empty() { ctx = context.WithValue(ctx, internal.KeyLabels, labels.Everything()) } else { - if sel, err := ui.TrimLabelSelector(x.CmdBuff().GetText()); err == nil { + if sel, err := ui.ExtractLabelSelector(x.CmdBuff().GetText()); err == nil { ctx = context.WithValue(ctx, internal.KeyLabels, sel) } } @@ -690,7 +690,7 @@ func (x *Xray) styleTitle() string { return title } if internal.IsLabelSelector(buff) { - if sel, err := ui.TrimLabelSelector(buff); err == nil { + if sel, err := ui.ExtractLabelSelector(buff); err == nil { buff = sel.String() } } diff --git a/internal/vul/scan.go b/internal/vul/scan.go index 2c746bf3..30d3e925 100644 --- a/internal/vul/scan.go +++ b/internal/vul/scan.go @@ -52,7 +52,10 @@ func (s *Scan) Dump(w io.Writer) error { func (s *Scan) run(mm *match.Matches, store vulnerability.MetadataProvider) error { for m := range mm.Enumerate() { - meta, err := store.VulnerabilityMetadata(vulnerability.Reference{ID: m.Vulnerability.ID, Namespace: m.Vulnerability.Namespace}) + meta, err := store.VulnerabilityMetadata(vulnerability.Reference{ + ID: m.Vulnerability.ID, + Namespace: m.Vulnerability.Namespace, + }) if err != nil { return err } diff --git a/internal/vul/scanner.go b/internal/vul/scanner.go index 7f2de16d..eb4b0f54 100644 --- a/internal/vul/scanner.go +++ b/internal/vul/scanner.go @@ -82,30 +82,37 @@ func (s *imageScanner) setScan(img string, sc *Scan) { // Init initializes image vulnerability database. func (s *imageScanner) Init(name, version string) { - s.mx.Lock() - defer s.mx.Unlock() + defer func(t time.Time) { + slog.Debug("VulDb initialization complete", + slogs.Elapsed, time.Since(t), + ) + }(time.Now()) - id := clio.Identification{Name: name, Version: version} - s.opts = options.DefaultGrype(id) - s.opts.GenerateMissingCPEs = true + opts := options.DefaultGrype(clio.Identification{Name: name, Version: version}) + opts.GenerateMissingCPEs = true - var err error - s.provider, s.status, err = grype.LoadVulnerabilityDB( - s.opts.ToClientConfig(), - s.opts.ToCuratorConfig(), - s.opts.DB.AutoUpdate, + provider, status, err := grype.LoadVulnerabilityDB( + opts.ToClientConfig(), + opts.ToCuratorConfig(), + opts.DB.AutoUpdate, ) if err != nil { s.log.Error("VulDb load failed", slogs.Error, err) return } + s.mx.Lock() + s.opts, s.provider, s.status = opts, provider, status + s.mx.Unlock() - if e := validateDBLoad(err, s.status); e != nil { + if e := validateDBLoad(err, status); e != nil { s.log.Error("VulDb validate failed", slogs.Error, e) return } + s.mx.Lock() s.initialized = true + s.mx.Unlock() + slog.Debug("VulDB initialized") } // Stop closes scan database. @@ -130,7 +137,7 @@ func (s *imageScanner) Score(ii ...string) string { return sc.String() } -func (s *imageScanner) isInitialized() bool { +func (s *imageScanner) IsInitialized() bool { s.mx.RLock() defer s.mx.RUnlock() @@ -138,9 +145,6 @@ func (s *imageScanner) isInitialized() bool { } func (s *imageScanner) Enqueue(ctx context.Context, images ...string) { - if !s.isInitialized() { - return - } ctx, cancel := context.WithTimeout(ctx, imgScanTimeout) defer cancel() diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 22ebabbc..b62e1cab 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core22 -version: 'v0.50.9' +version: 'v0.50.10' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session.