Rel v0.50.10 (#3564)

* fix #3455

* Clean up and refactor

* fix #3495 #3470 #3455 - fix cmd alias and filters

* fix #3535 - revert

* fix #3478 - make vulscan a bit less cpu extensive (init stab...)

* fix issue with plugin foreground exit

* fix#3466-add shared gpu on nodes

* fix #3541 - use default namespace when not specified

* update deps + release notes
mine
Fernand Galiana 2025-09-17 16:49:58 -06:00 committed by GitHub
parent e7970c2517
commit 4f5c1a1105
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
71 changed files with 879 additions and 391 deletions

View File

@ -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

View File

@ -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.

View File

@ -0,0 +1,82 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
# 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
---
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#

39
go.mod
View File

@ -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

98
go.sum
View File

@ -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=

View File

@ -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

View File

@ -32,7 +32,7 @@ func TestCallTimeout(t *testing.T) {
e: 1 * time.Minute,
},
"default": {
e: 15 * time.Second,
e: client.DefaultCallTimeoutDuration,
},
}

View File

@ -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.

View File

@ -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,
)

View File

@ -15,5 +15,4 @@ contexts:
name: blee
current-context: blee
kind: Config
preferences: {}
users: null

View File

@ -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.

View File

@ -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 (

View File

@ -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.

View File

@ -3,7 +3,7 @@ k9s:
gpuVendors: {}
screenDumpDir: /tmp/k9s-test/screen-dumps
refreshRate: 2
apiServerTimeout: 15s
apiServerTimeout: 2m0s
maxConnRetry: 5
readOnly: false
noExitOnCtrlC: false

View File

@ -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")

View File

@ -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)

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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

View File

@ -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)
}

View File

@ -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) {}

View File

@ -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.

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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])
}

View File

@ -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) {

View File

@ -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])
}

View File

@ -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 {

View File

@ -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])
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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", "<unknown>", "100", "100:0", "100", "n/a", "50", "70:170", "71", "29", "0:0", "172.17.0.6", "minikube", "default", "<none>"}
e := model1.Fields{"default", "nginx", "n/a", "●", "1/1", "Running", "0", "<unknown>", "100", "100:0", "100", "n/a", "50", "70:170", "71", "29", "0:0", "172.17.0.6", "minikube", "default", "<none>"}
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", "<unknown>", "10", "100:0", "10", "n/a", "10", "70:170", "14", "5", "0:0", "172.17.0.6", "minikube", "default", "<none>"}
e := model1.Fields{"default", "nginx", "n/a", "●", "1/1", "Init:0/1", "0", "<unknown>", "10", "100:0", "10", "n/a", "10", "70:170", "14", "5", "0:0", "172.17.0.6", "minikube", "default", "<none>"}
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", "<unknown>", "100", "50:250", "200", "40", "40", "50:80", "80", "50", "0:0", "10.244.0.8", "kind-control-plane", "default", "<none>"}
e := model1.Fields{"default", "sleep", "n/a", "●", "2/2", "Running", "0", "<unknown>", "100", "50:250", "200", "40", "40", "50:80", "80", "50", "0:0", "10.244.0.8", "kind-control-plane", "default", "<none>"}
assert.Equal(t, e, r.Fields[:21])
}

View File

@ -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])
}

View File

@ -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])
}

View File

@ -81,6 +81,7 @@ const (
// Context tracks a context logger key.
Context = "context"
// Cluster tracks a cluster logger key.
Cluster = "cluster"

View File

@ -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())
}

View File

@ -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) {}

View File

@ -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()
}
}

View File

@ -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))

View File

@ -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() {

View File

@ -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:])

View File

@ -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)
})

View File

@ -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 {

View File

@ -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)

View File

@ -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())
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -71,7 +71,7 @@ func TestFlagsNew(t *testing.T) {
"label-toast": {
i: NewInterpreter("po"),
aa: []string{"="},
ll: make(args),
ll: args{labelKey: "="},
},
"multi-labels": {

View File

@ -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) {

View File

@ -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])
}

View File

@ -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())
}
})
}
}

View File

@ -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(

View File

@ -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 {

View File

@ -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)
}
})
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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) {

View File

@ -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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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())
}

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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()

View File

@ -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.