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.