Rel v0.50.4 (#3293)

* fix#3278 honor ns ovewride

* refactor column exclude

* [feat] add configurable api server timeout

* clean

* fix#3285 - add restart option for flux support

* fix#3283 fix dp ready state

* update vulscan deps

* fix#3288 fix cust view filter

* fix#3286 match-exp

* rel notes
mine
Fernand Galiana 2025-04-19 12:07:10 -06:00 committed by GitHub
parent c2694ee3e5
commit e4e3816185
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
60 changed files with 428 additions and 286 deletions

View File

@ -239,77 +239,3 @@ formatters:
- internal/x # extracted from x/tools code - internal/x # extracted from x/tools code
- pkg/goformatters/gci/internal # extracted from gci code - pkg/goformatters/gci/internal # extracted from gci code
- pkg/goanalysis/runner_checker.go # extracted from x/tools code - pkg/goanalysis/runner_checker.go # extracted from x/tools code
# linters:
# default: none
# enable:
# - sloglint
# exclusions:
# generated: lax
# paths:
# - third_party$
# - builtin$
# - examples$
# - \\.(generated\\.deepcopy|pb)\\.go$
# settings:
# gocyclo:
# min-complexity: 35
# govet:
# enable:
# - nilness
# goimports:
# local-prefixes: github.com/derailed/k9s
# unused:
# parameters-are-used: true
# local-variables-are-used: true
# field-writes-are-uses: true
# post-statements-are-reads: true
# exported-fields-are-used: true
# generated-is-used: true
# goheader:
# values:
# regexp:
# PROJECT: 'K9s'
# template: |-
# SPDX-License-Identifier: Apache-2.0
# Copyright Authors of {{ PROJECT }}
# gosec:
# includes:
# - G402
# issues:
# # default is true. Enables skipping of directories:
# # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
# # exclude-dirs-use-default: true
# # Excluding configuration per-path, per-linter, per-text and per-source
# # exclude-rules:
# # - linters: [staticcheck]
# # text: "SA1019" # this is rule for deprecated method
# # - linters: [staticcheck]
# # text: "SA9003: empty branch"
# # - linters: [staticcheck]
# # text: "SA2001: empty critical section"
# # - linters: [err113]
# # text: "do not define dynamic errors, use wrapped static errors instead" # This rule to avoid opinionated check fmt.Errorf("text")
# # # Skip goimports check on generated files
# # - path: \\.(generated\\.deepcopy|pb)\\.go$
# # linters:
# # - goimports
# # # Skip goheader check on files imported and modified from upstream k8s
# # - path: "pkg/ipam/(cidrset|service)/.+\\.go"
# # linters:
# # - goheader

View File

@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
else else
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
endif endif
VERSION ?= v0.50.3 VERSION ?= v0.50.4
IMG_NAME := derailed/k9s IMG_NAME := derailed/k9s
IMAGE := ${IMG_NAME}:${VERSION} IMAGE := ${IMG_NAME}:${VERSION}

View File

@ -409,8 +409,10 @@ You can now override the context portForward default address configuration by se
liveViewAutoRefresh: false liveViewAutoRefresh: false
# The path to screen dump. Default: '%temp_dir%/k9s-screens-%username%' (k9s info) # The path to screen dump. Default: '%temp_dir%/k9s-screens-%username%' (k9s info)
screenDumpDir: /tmp/dumps screenDumpDir: /tmp/dumps
# Represents ui poll intervals. Default 2secs # Represents ui poll intervals in seconds. Default 2secs
refreshRate: 2 refreshRate: 2
# Overrides the default k8s api server requests timeout. Defaults 120s
apiServerTimeout: 15s
# Number of retries once the connection to the api-server is lost. Default 15. # Number of retries once the connection to the api-server is lost. Default 15.
maxConnRetry: 5 maxConnRetry: 5
# Indicates whether modification commands like delete/kill/edit are disabled. Default is false # Indicates whether modification commands like delete/kill/edit are disabled. Default is false
@ -431,6 +433,7 @@ You can now override the context portForward default address configuration by se
crumbsless: false crumbsless: false
# Set to true to suppress the K9s splash screen on start. Default false. Note that for larger clusters or higher latency connections, there may be no resources visible initially until local caches have finished populating. # Set to true to suppress the K9s splash screen on start. Default false. Note that for larger clusters or higher latency connections, there may be no resources visible initially until local caches have finished populating.
splashless: false splashless: false
# Toggles icons display as not all terminal support these chars. Default: true
noIcons: false noIcons: false
# Toggles reactive UI. This option provide for watching on disk artifacts changes and update the UI live Defaults to false. # Toggles reactive UI. This option provide for watching on disk artifacts changes and update the UI live Defaults to false.
reactive: false reactive: false

View File

@ -0,0 +1,44 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
# Release v0.50.4
## 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!
---
## Resolved Issues
* [#3288](https://github.com/derailed/k9s/issues/3288) Resource search doesn't filter by name in custom view
* [#3286](https://github.com/derailed/k9s/issues/3286) K9S doesn't understand matchExpressions selector in Deployment to Pod navigation
* [#3285](https://github.com/derailed/k9s/issues/3285) Rollout Restart method conflicts with GitOps (Flux, ArgoCD)
* [#3283](https://github.com/derailed/k9s/issues/3283) Deployment status showing wrong ready state
* [#3278](https://github.com/derailed/k9s/issues/3278) k9s doesn't honor the --namespace parameter
---
## 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!!
* [#3292](https://github.com/derailed/k9s/pull/3292) fix: respect insecure flag when switch context
* [#3277](https://github.com/derailed/k9s/pull/3277) feat: add hostPathVolume (docker)
* [#3253](https://github.com/derailed/k9s/pull/3253) fix: set default request timeout to 120 seconds
* [#2866](https://github.com/derailed/k9s/pull/2866) Feature/default_view
---
<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)

View File

@ -12,18 +12,17 @@ import (
"strings" "strings"
"time" "time"
"github.com/lmittmann/tint"
"github.com/mattn/go-colorable"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd/api"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/color"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/view" "github.com/derailed/k9s/internal/view"
"github.com/lmittmann/tint"
"github.com/mattn/go-colorable"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd/api"
) )
const ( const (

9
go.mod
View File

@ -5,7 +5,7 @@ go 1.24.1
require ( require (
github.com/adrg/xdg v0.5.3 github.com/adrg/xdg v0.5.3
github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716 github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716
github.com/anchore/grype v0.87.0 github.com/anchore/grype v0.91.0
github.com/anchore/syft v1.22.0 github.com/anchore/syft v1.22.0
github.com/atotto/clipboard v0.1.4 github.com/atotto/clipboard v0.1.4
github.com/cenkalti/backoff/v4 v4.3.0 github.com/cenkalti/backoff/v4 v4.3.0
@ -152,7 +152,7 @@ require (
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/encoding v1.0.1 // indirect
github.com/github/go-spdx/v2 v2.3.2 // indirect github.com/github/go-spdx/v2 v2.3.2 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/glebarez/sqlite v1.11.0 // indirect github.com/glebarez/sqlite v1.11.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect
@ -232,8 +232,6 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/locker v1.0.1 // indirect github.com/moby/locker v1.0.1 // indirect
@ -262,6 +260,7 @@ require (
github.com/openvex/go-vex v0.2.5 // indirect github.com/openvex/go-vex v0.2.5 // indirect
github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 // indirect github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 // indirect
github.com/package-url/packageurl-go v0.1.1 // indirect github.com/package-url/packageurl-go v0.1.1 // indirect
github.com/pandatix/go-cvss v0.6.2 // indirect
github.com/pborman/indent v1.2.1 // indirect github.com/pborman/indent v1.2.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
@ -336,7 +335,7 @@ require (
golang.org/x/sync v0.13.0 // indirect golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect golang.org/x/term v0.30.0 // indirect
golang.org/x/time v0.8.0 // indirect golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.31.0 // indirect golang.org/x/tools v0.31.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.215.0 // indirect google.golang.org/api v0.215.0 // indirect

18
go.sum
View File

@ -712,8 +712,8 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ= github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 h1:rmZG77uXgE+o2gozGEBoUMpX27lsku+xrMwlmBZJtbg= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 h1:rmZG77uXgE+o2gozGEBoUMpX27lsku+xrMwlmBZJtbg=
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
github.com/anchore/grype v0.87.0 h1:cdrNA4rnSMpBxP8NmKNF75VUlP/VWaOG8YJJjiIDwIs= github.com/anchore/grype v0.91.0 h1:x6/jweLDNp+jy6ufyCukBJbFAlVefxSUOqb2eFsJdQY=
github.com/anchore/grype v0.87.0/go.mod h1:Umw/9sZHnS+9mPLTnUFd/OzApR6dzE2CPlRsov7kSmQ= github.com/anchore/grype v0.91.0/go.mod h1:O+lJcLV4OWTPwA8Rvr8U1utnqqNA0WgvVgc0q0QbpJw=
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY= github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY=
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI= github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
github.com/anchore/stereoscope v0.1.2 h1:0+Jcf7hoImYKfrH2XzN6vvbmbpm68A/woabC43gZ4eU= github.com/anchore/stereoscope v0.1.2 h1:0+Jcf7hoImYKfrH2XzN6vvbmbpm68A/woabC43gZ4eU=
@ -1001,8 +1001,8 @@ github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZ
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
github.com/gkampitakis/go-snaps v0.5.11 h1:LFG0ggUKR+KEiiaOvFCmLgJ5NO2zf93AxxddkBn3LdQ= github.com/gkampitakis/go-snaps v0.5.11 h1:LFG0ggUKR+KEiiaOvFCmLgJ5NO2zf93AxxddkBn3LdQ=
github.com/gkampitakis/go-snaps v0.5.11/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY= github.com/gkampitakis/go-snaps v0.5.11/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
@ -1438,13 +1438,9 @@ github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJ
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
@ -1523,6 +1519,8 @@ github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 h1:FvA4bwjKp
github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554/go.mod h1:n73K/hcuJ50MiVznXyN4rde6fZY7naGKWBXOLFTyc94= github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554/go.mod h1:n73K/hcuJ50MiVznXyN4rde6fZY7naGKWBXOLFTyc94=
github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU= github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU=
github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
github.com/pandatix/go-cvss v0.6.2 h1:TFiHlzUkT67s6UkelHmK6s1INKVUG7nlKYiWWDTITGI=
github.com/pandatix/go-cvss v0.6.2/go.mod h1:jDXYlQBZrc8nvrMUVVvTG8PhmuShOnKrxP53nOFkt8Q=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM= github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM=
@ -2184,8 +2182,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -6,12 +6,14 @@ package client
import ( import (
"errors" "errors"
"fmt" "fmt"
"log/slog"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/derailed/k9s/internal/slogs"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
@ -19,7 +21,8 @@ import (
) )
const ( const (
defaultCallTimeoutDuration = 120 * time.Second // DefaultCallTimeoutDuration is the default api server call timeout duration.
DefaultCallTimeoutDuration time.Duration = 15 * time.Second
// UsePersistentConfig caches client config to avoid reloads. // UsePersistentConfig caches client config to avoid reloads.
UsePersistentConfig = true UsePersistentConfig = true
@ -42,12 +45,13 @@ func NewConfig(f *genericclioptions.ConfigFlags) *Config {
// CallTimeout returns the call timeout if set or the default if not set. // CallTimeout returns the call timeout if set or the default if not set.
func (c *Config) CallTimeout() time.Duration { func (c *Config) CallTimeout() time.Duration {
if !isSet(c.flags.Timeout) { if !isSet(c.flags.Timeout) {
return defaultCallTimeoutDuration return DefaultCallTimeoutDuration
} }
dur, err := time.ParseDuration(*c.flags.Timeout) dur, err := time.ParseDuration(*c.flags.Timeout)
if err != nil { if err != nil {
return defaultCallTimeoutDuration return DefaultCallTimeoutDuration
} }
slog.Debug("APIServer timeout", slogs.Duration, dur)
return dur return dur
} }
@ -95,6 +99,8 @@ func (c *Config) SwitchContext(name string) error {
flags.ImpersonateGroup = c.flags.ImpersonateGroup flags.ImpersonateGroup = c.flags.ImpersonateGroup
flags.ImpersonateUID = c.flags.ImpersonateUID flags.ImpersonateUID = c.flags.ImpersonateUID
flags.Insecure = c.flags.Insecure flags.Insecure = c.flags.Insecure
flags.BearerToken = c.flags.BearerToken
c.flags = flags c.flags = flags
return nil return nil

View File

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

View File

@ -17,7 +17,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
var NoGVR = &GVR{} var NoGVR = new(GVR)
// GVR represents a kubernetes resource schema as a string. // GVR represents a kubernetes resource schema as a string.
// Format is group/version/resources:subresource. // Format is group/version/resources:subresource.

View File

@ -34,7 +34,7 @@ var (
ScGVR = NewGVR("storage.k8s.io/v1/storageclasses") ScGVR = NewGVR("storage.k8s.io/v1/storageclasses")
// Policy... // Policy...
PdbGVR = NewGVR("policy/v1/PodDisruptionBudgets") PdbGVR = NewGVR("policy/v1/poddisruptionbudgets")
PspGVR = NewGVR("policy/v1beta1/podsecuritypolicies") PspGVR = NewGVR("policy/v1beta1/podsecuritypolicies")
// Metrics... // Metrics...

View File

@ -9,6 +9,7 @@ import (
"io/fs" "io/fs"
"log/slog" "log/slog"
"os" "os"
"time"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
@ -79,11 +80,24 @@ func (c *Config) ContextPluginsPath() (string, error) {
return AppContextPluginsFile(ct.GetClusterName(), c.K9s.activeContextName), nil return AppContextPluginsFile(ct.GetClusterName(), c.K9s.activeContextName), nil
} }
func setK8sTimeout(flags *genericclioptions.ConfigFlags, d time.Duration) {
v := d.String()
flags.Timeout = &v
}
// Refine the configuration based on cli args. // Refine the configuration based on cli args.
func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, cfg *client.Config) error { func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, cfg *client.Config) error {
if flags == nil { if flags == nil {
return nil return nil
} }
if !isStringSet(flags.Timeout) {
if d, err := time.ParseDuration(c.K9s.APIServerTimeout); err == nil {
setK8sTimeout(flags, d)
} else {
setK8sTimeout(flags, client.DefaultCallTimeoutDuration)
}
}
if isStringSet(flags.Context) { if isStringSet(flags.Context) {
if _, err := c.K9s.ActivateContext(*flags.Context); err != nil { if _, err := c.K9s.ActivateContext(*flags.Context); err != nil {
return fmt.Errorf("k8sflags. unable to activate context %q: %w", *flags.Context, err) return fmt.Errorf("k8sflags. unable to activate context %q: %w", *flags.Context, err)
@ -107,6 +121,7 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c
c.ResetActiveView() c.ResetActiveView()
case isStringSet(flags.Namespace): case isStringSet(flags.Namespace):
ns = *flags.Namespace ns = *flags.Namespace
c.ResetActiveView()
default: default:
nss, err := c.K9s.ActiveContextNamespace() nss, err := c.K9s.ActiveContextNamespace()
if err != nil { if err != nil {

View File

@ -49,9 +49,8 @@ func TestConfigSave(t *testing.T) {
}, },
} }
for k := range uu { for k, u := range uu {
xdg.Reload() xdg.Reload()
u := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
c := mock.NewMockConfig(t) c := mock.NewMockConfig(t)
_, err := c.K9s.ActivateContext(u.ct) _, err := c.K9s.ActivateContext(u.ct)
@ -562,6 +561,7 @@ func TestConfigSaveFile(t *testing.T) {
require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true)) require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
cfg.K9s.RefreshRate = 100 cfg.K9s.RefreshRate = 100
cfg.K9s.APIServerTimeout = "30s"
cfg.K9s.ReadOnly = true cfg.K9s.ReadOnly = true
cfg.K9s.Logger.TailCount = 500 cfg.K9s.Logger.TailCount = 500
cfg.K9s.Logger.BufferSize = 800 cfg.K9s.Logger.BufferSize = 800

View File

@ -10,6 +10,7 @@
"liveViewAutoRefresh": { "type": "boolean" }, "liveViewAutoRefresh": { "type": "boolean" },
"screenDumpDir": {"type": "string"}, "screenDumpDir": {"type": "string"},
"refreshRate": { "type": "integer" }, "refreshRate": { "type": "integer" },
"apiServerTimeout": { "type": "string" },
"maxConnRetry": { "type": "integer" }, "maxConnRetry": { "type": "integer" },
"readOnly": { "type": "boolean" }, "readOnly": { "type": "boolean" },
"noExitOnCtrlC": { "type": "boolean" }, "noExitOnCtrlC": { "type": "boolean" },

View File

@ -24,6 +24,7 @@ type K9s struct {
LiveViewAutoRefresh bool `json:"liveViewAutoRefresh" yaml:"liveViewAutoRefresh"` LiveViewAutoRefresh bool `json:"liveViewAutoRefresh" yaml:"liveViewAutoRefresh"`
ScreenDumpDir string `json:"screenDumpDir" yaml:"screenDumpDir,omitempty"` ScreenDumpDir string `json:"screenDumpDir" yaml:"screenDumpDir,omitempty"`
RefreshRate int `json:"refreshRate" yaml:"refreshRate"` RefreshRate int `json:"refreshRate" yaml:"refreshRate"`
APIServerTimeout string `json:"apiServerTimeout" yaml:"apiServerTimeout"`
MaxConnRetry int32 `json:"maxConnRetry" yaml:"maxConnRetry"` MaxConnRetry int32 `json:"maxConnRetry" yaml:"maxConnRetry"`
ReadOnly bool `json:"readOnly" yaml:"readOnly"` ReadOnly bool `json:"readOnly" yaml:"readOnly"`
NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"` NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"`
@ -54,6 +55,7 @@ func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s {
return &K9s{ return &K9s{
RefreshRate: defaultRefreshRate, RefreshRate: defaultRefreshRate,
MaxConnRetry: defaultMaxConnRetry, MaxConnRetry: defaultMaxConnRetry,
APIServerTimeout: client.DefaultCallTimeoutDuration.String(),
ScreenDumpDir: AppDumpsDir, ScreenDumpDir: AppDumpsDir,
Logger: NewLogger(), Logger: NewLogger(),
Thresholds: NewThreshold(), Thresholds: NewThreshold(),
@ -117,9 +119,11 @@ func (k *K9s) Merge(k1 *K9s) {
k.DefaultView = k1.DefaultView k.DefaultView = k1.DefaultView
k.ScreenDumpDir = k1.ScreenDumpDir k.ScreenDumpDir = k1.ScreenDumpDir
k.RefreshRate = k1.RefreshRate k.RefreshRate = k1.RefreshRate
k.APIServerTimeout = k1.APIServerTimeout
k.MaxConnRetry = k1.MaxConnRetry k.MaxConnRetry = k1.MaxConnRetry
k.ReadOnly = k1.ReadOnly k.ReadOnly = k1.ReadOnly
k.NoExitOnCtrlC = k1.NoExitOnCtrlC k.NoExitOnCtrlC = k1.NoExitOnCtrlC
k.PortForwardAddress = k1.PortForwardAddress
k.UI = k1.UI k.UI = k1.UI
k.SkipLatestRevCheck = k1.SkipLatestRevCheck k.SkipLatestRevCheck = k1.SkipLatestRevCheck
k.DisablePodCounting = k1.DisablePodCounting k.DisablePodCounting = k1.DisablePodCounting

View File

@ -8,7 +8,7 @@ const (
DefaultLoggerTailCount = 100 DefaultLoggerTailCount = 100
// MaxLogThreshold sets the max value for log size. // MaxLogThreshold sets the max value for log size.
MaxLogThreshold = 5000 MaxLogThreshold = 5_000
// DefaultSinceSeconds tracks default log age. // DefaultSinceSeconds tracks default log age.
DefaultSinceSeconds = -1 // tail logs by default DefaultSinceSeconds = -1 // tail logs by default

View File

@ -2,6 +2,7 @@ k9s:
liveViewAutoRefresh: false liveViewAutoRefresh: false
screenDumpDir: /tmp/k9s-test/screen-dumps screenDumpDir: /tmp/k9s-test/screen-dumps
refreshRate: 2 refreshRate: 2
apiServerTimeout: 15s
maxConnRetry: 5 maxConnRetry: 5
readOnly: false readOnly: false
noExitOnCtrlC: false noExitOnCtrlC: false

View File

@ -2,6 +2,7 @@ k9s:
liveViewAutoRefresh: true liveViewAutoRefresh: true
screenDumpDir: /tmp/k9s-test/screen-dumps screenDumpDir: /tmp/k9s-test/screen-dumps
refreshRate: 100 refreshRate: 100
apiServerTimeout: 30s
maxConnRetry: 5 maxConnRetry: 5
readOnly: true readOnly: true
noExitOnCtrlC: false noExitOnCtrlC: false

View File

@ -2,6 +2,7 @@ k9s:
liveViewAutoRefresh: true liveViewAutoRefresh: true
screenDumpDir: /tmp/k9s-test/screen-dumps screenDumpDir: /tmp/k9s-test/screen-dumps
refreshRate: 2 refreshRate: 2
apiServerTimeout: 10s
maxConnRetry: 5 maxConnRetry: 5
readOnly: false readOnly: false
noExitOnCtrlC: false noExitOnCtrlC: false

View File

@ -56,8 +56,8 @@ func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) err
} }
// Restart a Deployment rollout. // Restart a Deployment rollout.
func (d *Deployment) Restart(ctx context.Context, path string) error { func (d *Deployment) Restart(ctx context.Context, path string, opts *metav1.PatchOptions) error {
return restartRes[*appsv1.Deployment](ctx, d.getFactory(), client.DpGVR, path) return restartRes[*appsv1.Deployment](ctx, d.getFactory(), client.DpGVR, path, opts)
} }
// TailLogs tail logs for all pods represented by this Deployment. // TailLogs tail logs for all pods represented by this Deployment.
@ -395,7 +395,7 @@ func scaleRes(ctx context.Context, f Factory, gvr *client.GVR, path string, repl
} }
} }
func restartRes[T runtime.Object](ctx context.Context, f Factory, gvr *client.GVR, path string) error { func restartRes[T runtime.Object](ctx context.Context, f Factory, gvr *client.GVR, path string, opts *metav1.PatchOptions) error {
o, err := f.Get(gvr, path, true, labels.Everything()) o, err := f.Get(gvr, path, true, labels.Everything())
if err != nil { if err != nil {
return err return err
@ -440,7 +440,7 @@ func restartRes[T runtime.Object](ctx context.Context, f Factory, gvr *client.GV
n, n,
types.StrategicMergePatchType, types.StrategicMergePatchType,
diff, diff,
metav1.PatchOptions{}, *opts,
) )
case client.DsGVR: case client.DsGVR:
@ -449,7 +449,7 @@ func restartRes[T runtime.Object](ctx context.Context, f Factory, gvr *client.GV
n, n,
types.StrategicMergePatchType, types.StrategicMergePatchType,
diff, diff,
metav1.PatchOptions{}, *opts,
) )
case client.StsGVR: case client.StsGVR:
@ -458,7 +458,7 @@ func restartRes[T runtime.Object](ctx context.Context, f Factory, gvr *client.GV
n, n,
types.StrategicMergePatchType, types.StrategicMergePatchType,
diff, diff,
metav1.PatchOptions{}, *opts,
) )
} }

View File

@ -50,8 +50,8 @@ func (d *DaemonSet) ListImages(_ context.Context, fqn string) ([]string, error)
} }
// Restart a DaemonSet rollout. // Restart a DaemonSet rollout.
func (d *DaemonSet) Restart(ctx context.Context, path string) error { func (d *DaemonSet) Restart(ctx context.Context, path string, opts *metav1.PatchOptions) error {
return restartRes[*appsv1.DaemonSet](ctx, d.getFactory(), client.DsGVR, path) return restartRes[*appsv1.DaemonSet](ctx, d.getFactory(), client.DsGVR, path, opts)
} }
// TailLogs tail logs for all pods represented by this DaemonSet. // TailLogs tail logs for all pods represented by this DaemonSet.

View File

@ -13,6 +13,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
@ -39,7 +40,11 @@ func (d *Dynamic) List(ctx context.Context, ns string) ([]runtime.Object, error)
} }
func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, error) { func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, error) {
strLabel, _ := ctx.Value(internal.KeyLabels).(string) sel := labels.Everything()
if s, ok := ctx.Value(internal.KeyLabels).(labels.Selector); ok {
sel = s
}
opts := []string{d.gvr.AsResourceName()} opts := []string{d.gvr.AsResourceName()}
ns, n := client.Namespaced(fqn) ns, n := client.Namespaced(fqn)
if n != "" { if n != "" {
@ -51,7 +56,7 @@ func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, er
b := f.NewBuilder(). b := f.NewBuilder().
Unstructured(). Unstructured().
NamespaceParam(ns).DefaultNamespace().AllNamespaces(allNS). NamespaceParam(ns).DefaultNamespace().AllNamespaces(allNS).
LabelSelectorParam(strLabel). LabelSelectorParam(sel.String()).
FieldSelectorParam(""). FieldSelectorParam("").
RequestChunksOf(0). RequestChunksOf(0).
ResourceTypeOrNameArgs(true, opts...). ResourceTypeOrNameArgs(true, opts...).

View File

@ -11,6 +11,7 @@ import (
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
) )
@ -38,7 +39,10 @@ type Generic struct {
// List returns a collection of resources. // List returns a collection of resources.
// BOZO!! no auth check?? // BOZO!! no auth check??
func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) { func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) {
labelSel, _ := ctx.Value(internal.KeyLabels).(string) labelSel, ok := ctx.Value(internal.KeyLabels).(labels.Selector)
if !ok {
labelSel = labels.Everything()
}
if client.IsAllNamespace(ns) { if client.IsAllNamespace(ns) {
ns = client.BlankNamespace ns = client.BlankNamespace
} }
@ -48,11 +52,12 @@ func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error)
return nil, err return nil, err
} }
opts := metav1.ListOptions{LabelSelector: labelSel.String()}
var ll *unstructured.UnstructuredList var ll *unstructured.UnstructuredList
if client.IsClusterScoped(ns) { if client.IsClusterScoped(ns) {
ll, err = dial.List(ctx, metav1.ListOptions{LabelSelector: labelSel}) ll, err = dial.List(ctx, opts)
} else { } else {
ll, err = dial.Namespace(ns).List(ctx, metav1.ListOptions{LabelSelector: labelSel}) ll, err = dial.Namespace(ns).List(ctx, opts)
} }
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -25,12 +25,9 @@ type Resource struct {
// List returns a collection of resources. // List returns a collection of resources.
func (r *Resource) List(ctx context.Context, ns string) ([]runtime.Object, error) { func (r *Resource) List(ctx context.Context, ns string) ([]runtime.Object, error) {
strLabel, _ := ctx.Value(internal.KeyLabels).(string)
lsel := labels.Everything() lsel := labels.Everything()
if strLabel != "" { if sel, ok := ctx.Value(internal.KeyLabels).(labels.Selector); ok {
if sel, err := labels.Parse(strLabel); err == nil { lsel = sel
lsel = sel
}
} }
return r.getFactory().List(r.gvr, ns, false, lsel) return r.getFactory().List(r.gvr, ns, false, lsel)

View File

@ -54,8 +54,8 @@ func (s *StatefulSet) Scale(ctx context.Context, path string, replicas int32) er
} }
// Restart a StatefulSet rollout. // Restart a StatefulSet rollout.
func (s *StatefulSet) Restart(ctx context.Context, path string) error { func (s *StatefulSet) Restart(ctx context.Context, path string, opts *metav1.PatchOptions) error {
return restartRes[*appsv1.StatefulSet](ctx, s.getFactory(), client.StsGVR, path) return restartRes[*appsv1.StatefulSet](ctx, s.getFactory(), client.StsGVR, path, opts)
} }
// GetInstance returns a statefulset instance. // GetInstance returns a statefulset instance.

View File

@ -12,6 +12,7 @@ import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
@ -56,7 +57,10 @@ func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
// List all Resources in a given namespace. // List all Resources in a given namespace.
func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) { func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
labelSel, _ := ctx.Value(internal.KeyLabels).(string) sel := labels.Everything()
if labelSel, ok := ctx.Value(internal.KeyLabels).(labels.Selector); ok {
sel = labelSel
}
fieldSel, _ := ctx.Value(internal.KeyFields).(string) fieldSel, _ := ctx.Value(internal.KeyFields).(string)
includeObject := includeMeta includeObject := includeMeta
@ -75,7 +79,7 @@ func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
Namespace(ns). Namespace(ns).
Resource(t.gvr.R()). Resource(t.gvr.R()).
VersionedParams(&metav1.ListOptions{ VersionedParams(&metav1.ListOptions{
LabelSelector: labelSel, LabelSelector: sel.String(),
FieldSelector: fieldSel, FieldSelector: fieldSel,
}, metav1.ParameterCodec). }, metav1.ParameterCodec).
Do(ctx).Get() Do(ctx).Get()

View File

@ -145,7 +145,7 @@ type Switchable interface {
// Restartable represents a restartable resource. // Restartable represents a restartable resource.
type Restartable interface { type Restartable interface {
// Restart performs a rollout restart. // Restart performs a rollout restart.
Restart(ctx context.Context, path string) error Restart(context.Context, string, *metav1.PatchOptions) error
} }
// Runnable represents a runnable resource. // Runnable represents a runnable resource.

View File

@ -13,6 +13,7 @@ import (
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/labels"
) )
func init() { func init() {
@ -313,4 +314,4 @@ func (c) Start() {}
func (c) Stop() {} func (c) Stop() {}
func (c) Init(context.Context) error { return nil } func (c) Init(context.Context) error { return nil }
func (c) SetFilter(string) {} func (c) SetFilter(string) {}
func (c) SetLabelFilter(map[string]string) {} func (c) SetLabelSelector(labels.Selector) {}

View File

@ -19,6 +19,7 @@ import (
"github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -38,15 +39,15 @@ type TableListener interface {
// Table represents a table model. // Table represents a table model.
type Table struct { type Table struct {
gvr *client.GVR gvr *client.GVR
data *model1.TableData data *model1.TableData
listeners []TableListener listeners []TableListener
inUpdate int32 inUpdate int32
refreshRate time.Duration refreshRate time.Duration
instance string instance string
labelFilter string labelSelector labels.Selector
mx sync.RWMutex mx sync.RWMutex
vs *config.ViewSetting vs *config.ViewSetting
} }
// NewTable returns a new table model. // NewTable returns a new table model.
@ -70,20 +71,20 @@ func (t *Table) SetViewSetting(ctx context.Context, vs *config.ViewSetting) {
} }
} }
// SetLabelFilter sets the labels filter. // SetLabelSelector sets the labels selector.
func (t *Table) SetLabelFilter(f string) { func (t *Table) SetLabelSelector(sel labels.Selector) {
t.mx.Lock() t.mx.Lock()
defer t.mx.Unlock() defer t.mx.Unlock()
t.labelFilter = f t.labelSelector = sel
} }
// GetLabelFilter sets the labels filter. // GetLabelSelector sets the labels selector.
func (t *Table) GetLabelFilter() string { func (t *Table) GetLabelSelector() labels.Selector {
t.mx.Lock() t.mx.Lock()
defer t.mx.Unlock() defer t.mx.Unlock()
return t.labelFilter return t.labelSelector
} }
// SetInstance sets a single entry table. // SetInstance sets a single entry table.
@ -253,7 +254,7 @@ func (t *Table) list(ctx context.Context, a dao.Accessor) ([]runtime.Object, err
a.Init(factory, t.gvr) a.Init(factory, t.gvr)
t.mx.RLock() t.mx.RLock()
ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter) ctx = context.WithValue(ctx, internal.KeyLabels, t.labelSelector)
t.mx.RUnlock() t.mx.RUnlock()
ns := client.CleanseNamespace(t.data.GetNamespace()) ns := client.CleanseNamespace(t.data.GetNamespace())
@ -273,7 +274,7 @@ func (t *Table) reconcile(ctx context.Context) error {
if t.vs != nil { if t.vs != nil {
meta.DAO.SetIncludeObject(true) meta.DAO.SetIncludeObject(true)
} }
ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter) ctx = context.WithValue(ctx, internal.KeyLabels, t.labelSelector)
if t.instance == "" { if t.instance == "" {
oo, err = t.list(ctx, meta.DAO) oo, err = t.list(ctx, meta.DAO)
} else { } else {

View File

@ -13,6 +13,7 @@ import (
"github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -105,7 +106,7 @@ type Viewer interface {
type Filterer interface { type Filterer interface {
SetFilter(string) SetFilter(string)
SetLabelFilter(map[string]string) SetLabelSelector(labels.Selector)
} }
// Cruder performs crud operations. // Cruder performs crud operations.

View File

@ -8,7 +8,9 @@ import (
"log/slog" "log/slog"
"reflect" "reflect"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"k8s.io/apimachinery/pkg/util/sets"
) )
const ageCol = "AGE" const ageCol = "AGE"
@ -171,6 +173,24 @@ func (h Header) Diff(header Header) bool {
return !reflect.DeepEqual(h, header) return !reflect.DeepEqual(h, header)
} }
// FilterColIndices return viewable col header indices.
func (h Header) FilterColIndices(ns string, wide bool) sets.Set[int] {
if len(h) == 0 {
return nil
}
nsed := client.IsNamespaced(ns)
cc := sets.New[int]()
for i, c := range h {
if c.Name == "AGE" || !wide && c.Wide || c.Hide || (nsed && c.Name == "NAMESPACE") {
continue
}
cc.Insert(i)
}
return cc
}
// ColumnNames return header col names // ColumnNames return header col names
func (h Header) ColumnNames(wide bool) []string { func (h Header) ColumnNames(wide bool) []string {
if len(h) == 0 { if len(h) == 0 {

View File

@ -164,6 +164,10 @@ func (t *TableData) Filter(f FilterOpts) *TableData {
} }
func (t *TableData) rxFilter(q string, inverse bool) (*RowEvents, error) { func (t *TableData) rxFilter(q string, inverse bool) (*RowEvents, error) {
if strings.Contains(q, " ") {
return t.rowEvents, nil
}
if inverse { if inverse {
q = q[1:] q = q[1:]
} }
@ -172,20 +176,16 @@ func (t *TableData) rxFilter(q string, inverse bool) (*RowEvents, error) {
return nil, fmt.Errorf("invalid rx filter %q: %w", q, err) return nil, fmt.Errorf("invalid rx filter %q: %w", q, err)
} }
var startIndex int vidx := t.header.FilterColIndices(t.namespace, true)
if _, ok := t.header.IndexOf("NAMESPACE", true); ok && client.IsNamespaced(t.namespace) {
startIndex = 1
}
rr := NewRowEvents(t.RowCount() / 2) rr := NewRowEvents(t.RowCount() / 2)
ageIndex, _ := t.header.IndexOf("AGE", true)
t.rowEvents.Range(func(_ int, re RowEvent) bool { t.rowEvents.Range(func(_ int, re RowEvent) bool {
ff := make([]string, 0, len(re.Row.Fields)) ff := make([]string, 0, len(re.Row.Fields))
for _, r := range re.Row.Fields[startIndex:] { for idx, r := range re.Row.Fields {
if !vidx.Has(idx) {
continue
}
ff = append(ff, r) ff = append(ff, r)
} }
if ageIndex >= 0 && startIndex != ageIndex && ageIndex+1 <= len(ff) {
ff = append(ff[0:ageIndex], ff[ageIndex+1:]...)
}
match := rx.MatchString(strings.Join(ff, spacer)) match := rx.MatchString(strings.Join(ff, spacer))
if (inverse && !match) || (!inverse && match) { if (inverse && !match) || (!inverse && match) {
rr.Add(re) rr.Add(re)

View File

@ -87,16 +87,20 @@ func (d Deployment) defaultRow(raw *unstructured.Unstructured, r *model1.Row) er
return err return err
} }
var desired int32
if dp.Spec.Replicas != nil {
desired = *dp.Spec.Replicas
}
r.ID = client.MetaFQN(&dp.ObjectMeta) r.ID = client.MetaFQN(&dp.ObjectMeta)
r.Fields = model1.Fields{ r.Fields = model1.Fields{
dp.Namespace, dp.Namespace,
dp.Name, dp.Name,
computeVulScore(dp.Namespace, dp.Labels, &dp.Spec.Template.Spec), computeVulScore(dp.Namespace, dp.Labels, &dp.Spec.Template.Spec),
strconv.Itoa(int(dp.Status.AvailableReplicas)) + "/" + strconv.Itoa(int(dp.Status.Replicas)), strconv.Itoa(int(dp.Status.AvailableReplicas)) + "/" + strconv.Itoa(int(desired)),
strconv.Itoa(int(dp.Status.UpdatedReplicas)), strconv.Itoa(int(dp.Status.UpdatedReplicas)),
strconv.Itoa(int(dp.Status.AvailableReplicas)), strconv.Itoa(int(dp.Status.AvailableReplicas)),
mapToStr(dp.Labels), mapToStr(dp.Labels),
AsStatus(d.diagnose(dp.Status.Replicas, dp.Status.AvailableReplicas)), AsStatus(d.diagnose(desired, dp.Status.AvailableReplicas)),
ToAge(dp.GetCreationTimestamp()), ToAge(dp.GetCreationTimestamp()),
} }

View File

@ -215,4 +215,7 @@ const (
// JQExp tracks a jq expression logger key. // JQExp tracks a jq expression logger key.
JQExp = "jq-exp" JQExp = "jq-exp"
// Duration tracks a duration logger key.
Duration = "duration"
) )

View File

@ -15,6 +15,7 @@ import (
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/labels"
) )
func init() { func init() {
@ -60,4 +61,4 @@ func (c) Start() {}
func (c) Stop() {} func (c) Stop() {}
func (c) Init(context.Context) error { return nil } func (c) Init(context.Context) error { return nil }
func (c) SetFilter(string) {} func (c) SetFilter(string) {}
func (c) SetLabelFilter(map[string]string) {} func (c) SetLabelSelector(labels.Selector) {}

View File

@ -0,0 +1,70 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dialog
import (
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/tview"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type RestartFn func(*metav1.PatchOptions) bool
type RestartDialogOpts struct {
Title, Message string
FieldManager string
Ack RestartFn
Cancel cancelFunc
}
func ShowRestart(styles *config.Dialog, pages *ui.Pages, opts *RestartDialogOpts) {
f := tview.NewForm()
f.SetItemPadding(0)
f.SetButtonsAlign(tview.AlignCenter).
SetButtonBackgroundColor(styles.ButtonBgColor.Color()).
SetButtonTextColor(styles.ButtonFgColor.Color()).
SetLabelColor(styles.LabelFgColor.Color()).
SetFieldTextColor(styles.FieldFgColor.Color())
f.AddButton("Cancel", func() {
dismissConfirm(pages)
opts.Cancel()
})
modal := tview.NewModalForm("<"+opts.Title+">", f)
args := metav1.PatchOptions{
FieldManager: opts.FieldManager,
}
f.AddInputField("FieldManager:", args.FieldManager, 40, nil, func(v string) {
args.FieldManager = v
})
f.AddButton("OK", func() {
if !opts.Ack(&args) {
return
}
dismissConfirm(pages)
opts.Cancel()
})
for i := range 2 {
b := f.GetButton(i)
if b == nil {
continue
}
b.SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color())
b.SetLabelColorActivated(styles.ButtonFocusFgColor.Color())
}
f.SetFocus(0)
message := opts.Message
modal.SetText(message)
modal.SetTextColor(styles.FgColor.Color())
modal.SetDoneFunc(func(int, string) {
dismissConfirm(pages)
opts.Cancel()
})
pages.AddPage(confirmKey, modal, false, false)
pages.ShowPage(confirmKey)
}

View File

@ -315,12 +315,18 @@ func (t *Table) doUpdate(data *model1.TableData) *model1.TableData {
} else { } else {
t.actions.Delete(KeyShiftP) t.actions.Delete(KeyShiftP)
} }
t.setSortCol(data.ComputeSortCol(t.GetViewSetting(), t.getSortCol(), t.getMSort())) t.setSortCol(data.ComputeSortCol(t.GetViewSetting(), t.getSortCol(), t.getMSort()))
return data return data
} }
func (t *Table) shouldExcludeColumn(h model1.HeaderColumn) bool {
return (h.Hide || (!t.wide && h.Wide)) ||
(h.Name == "NAMESPACE" && !t.GetModel().ClusterWide()) ||
(h.MX && !t.hasMetrics) ||
(h.VS && vul.ImgScanner == nil)
}
func (t *Table) UpdateUI(cdata, data *model1.TableData) { func (t *Table) UpdateUI(cdata, data *model1.TableData) {
t.Clear() t.Clear()
fg := t.styles.Table().Header.FgColor.Color() fg := t.styles.Table().Header.FgColor.Color()
@ -328,19 +334,9 @@ func (t *Table) UpdateUI(cdata, data *model1.TableData) {
var col int var col int
for _, h := range cdata.Header() { for _, h := range cdata.Header() {
if h.Hide || (!t.wide && h.Wide) { if t.shouldExcludeColumn(h) {
continue continue
} }
if h.Name == "NAMESPACE" && !t.GetModel().ClusterWide() {
continue
}
if h.MX && !t.hasMetrics {
continue
}
if h.VS && vul.ImgScanner == nil {
continue
}
t.AddHeaderCell(col, h) t.AddHeaderCell(col, h)
c := t.GetCell(0, col) c := t.GetCell(0, col)
c.SetBackgroundColor(bg) c.SetBackgroundColor(bg)
@ -384,17 +380,7 @@ func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads M
) )
continue continue
} }
if h[c].Hide || (!t.wide && h[c].Wide) { if t.shouldExcludeColumn(h[c]) {
continue
}
if h[c].Name == "NAMESPACE" && !t.GetModel().ClusterWide() {
continue
}
if h[c].MX && !t.hasMetrics {
continue
}
if h[c].VS && vul.ImgScanner == nil {
continue continue
} }
@ -468,7 +454,6 @@ func (t *Table) Refresh() {
if data.HeaderCount() == 0 { if data.HeaderCount() == 0 {
return return
} }
// BOZO!! Really want to tell model reload now. Refactor!
cdata := t.Update(data, t.hasMetrics) cdata := t.Update(data, t.hasMetrics)
t.UpdateUI(cdata, data) t.UpdateUI(cdata, data)
} }
@ -575,9 +560,14 @@ func (t *Table) styleTitle() string {
buff := t.cmdBuff.GetText() buff := t.cmdBuff.GetText()
if internal.IsLabelSelector(buff) { if internal.IsLabelSelector(buff) {
buff = render.Truncate(TrimLabelSelector(buff), maxTruncate) sel, err := TrimLabelSelector(buff)
} else if l := t.GetModel().GetLabelFilter(); l != "" { if err != nil {
buff = render.Truncate(l, maxTruncate) buff = render.Truncate(sel.String(), maxTruncate)
}
} else if l := t.GetModel().GetLabelSelector(); l != nil && !l.Empty() {
buff = render.Truncate(l.String(), maxTruncate)
} else if buff != "" {
buff = render.Truncate(buff, maxTruncate)
} }
if buff == "" { if buff == "" {

View File

@ -13,6 +13,7 @@ import (
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"k8s.io/apimachinery/pkg/labels"
) )
const ( const (
@ -58,12 +59,13 @@ func TrimCell(tv *SelectTable, row, col int) string {
} }
// TrimLabelSelector extracts label query. // TrimLabelSelector extracts label query.
func TrimLabelSelector(s string) string { func TrimLabelSelector(s string) (labels.Selector, error) {
var selStr string
if strings.Index(s, "-l") == 0 { if strings.Index(s, "-l") == 0 {
return strings.TrimSpace(s[2:]) selStr = strings.TrimSpace(s[2:])
} }
return s return labels.Parse(selStr)
} }
// SkinTitle decorates a title. // SkinTitle decorates a title.

View File

@ -8,6 +8,7 @@ import (
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/labels"
) )
func TestTruncate(t *testing.T) { func TestTruncate(t *testing.T) {
@ -34,17 +35,29 @@ func TestTruncate(t *testing.T) {
} }
func TestTrimLabelSelector(t *testing.T) { func TestTrimLabelSelector(t *testing.T) {
sel, _ := labels.Parse("app=fred,env=blee")
uu := map[string]struct { uu := map[string]struct {
sel, e string sel string
err error
e labels.Selector
}{ }{
"cool": {"-l app=fred,env=blee", "app=fred,env=blee"}, "cool": {
"noSpace": {"-lapp=fred,env=blee", "app=fred,env=blee"}, sel: "-l app=fred,env=blee",
e: sel,
},
"no-space": {
sel: "-lapp=fred,env=blee",
e: sel,
},
} }
for k := range uu { for k := range uu {
u := uu[k] u := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, TrimLabelSelector(u.sel)) sel, err := TrimLabelSelector(u.sel)
assert.Equal(t, u.err, err)
assert.Equal(t, u.e, sel)
}) })
} }
} }

View File

@ -17,6 +17,7 @@ import (
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -42,7 +43,7 @@ func TestTableUpdate(t *testing.T) {
func TestTableSelection(t *testing.T) { func TestTableSelection(t *testing.T) {
v := ui.NewTable(client.NewGVR("fred")) v := ui.NewTable(client.NewGVR("fred"))
v.Init(makeContext()) v.Init(makeContext())
m := &mockModel{} m := new(mockModel)
v.SetModel(m) v.SetModel(m)
data := m.Peek() data := m.Peek()
cdata := v.Update(data, false) cdata := v.Update(data, false)
@ -72,8 +73,8 @@ var _ ui.Tabular = &mockModel{}
func (*mockModel) SetViewSetting(context.Context, *config.ViewSetting) {} func (*mockModel) SetViewSetting(context.Context, *config.ViewSetting) {}
func (*mockModel) SetInstance(string) {} func (*mockModel) SetInstance(string) {}
func (*mockModel) SetLabelFilter(string) {} func (*mockModel) SetLabelSelector(labels.Selector) {}
func (*mockModel) GetLabelFilter() string { return "" } func (*mockModel) GetLabelSelector() labels.Selector { return nil }
func (*mockModel) Empty() bool { return false } func (*mockModel) Empty() bool { return false }
func (*mockModel) RowCount() int { return 1 } func (*mockModel) RowCount() int { return 1 }
func (*mockModel) HasMetrics() bool { return true } func (*mockModel) HasMetrics() bool { return true }

View File

@ -12,6 +12,7 @@ import (
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/model1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -49,11 +50,11 @@ type Tabular interface {
// SetInstance sets parent resource path. // SetInstance sets parent resource path.
SetInstance(string) SetInstance(string)
// SetLabelFilter sets the label filter. // SetLabelSelector sets the label selector.
SetLabelFilter(string) SetLabelSelector(labels.Selector)
// GetLabelFilter fetch the label filter. // GetLabelSelector fetch the label filter.
GetLabelFilter() string GetLabelSelector() labels.Selector
// Empty returns true if model has no data. // Empty returns true if model has no data.
Empty() bool Empty() bool

View File

@ -21,6 +21,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -35,7 +36,7 @@ func TestAliasNew(t *testing.T) {
func TestAliasSearch(t *testing.T) { func TestAliasSearch(t *testing.T) {
v := view.NewAlias(client.AliGVR) v := view.NewAlias(client.AliGVR)
require.NoError(t, v.Init(makeContext(t))) require.NoError(t, v.Init(makeContext(t)))
v.GetTable().SetModel(&mockModel{}) v.GetTable().SetModel(new(mockModel))
v.GetTable().Refresh() v.GetTable().Refresh()
v.App().Prompt().SetModel(v.GetTable().CmdBuff()) v.App().Prompt().SetModel(v.GetTable().CmdBuff())
v.App().Prompt().SendStrokes("blee") v.App().Prompt().SendStrokes("blee")
@ -93,8 +94,8 @@ func (*mockModel) NextSuggestion() (string, bool) { return
func (*mockModel) PrevSuggestion() (string, bool) { return "", false } func (*mockModel) PrevSuggestion() (string, bool) { return "", false }
func (*mockModel) ClearSuggestions() {} func (*mockModel) ClearSuggestions() {}
func (*mockModel) SetInstance(string) {} func (*mockModel) SetInstance(string) {}
func (*mockModel) SetLabelFilter(string) {} func (*mockModel) SetLabelSelector(labels.Selector) {}
func (*mockModel) GetLabelFilter() string { return "" } func (*mockModel) GetLabelSelector() labels.Selector { return nil }
func (*mockModel) Empty() bool { return false } func (*mockModel) Empty() bool { return false }
func (*mockModel) RowCount() int { return 1 } func (*mockModel) RowCount() int { return 1 }
func (*mockModel) HasMetrics() bool { return true } func (*mockModel) HasMetrics() bool { return true }

View File

@ -26,6 +26,7 @@ import (
"github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
) )
@ -194,9 +195,11 @@ func (b *Browser) SetFilter(s string) {
b.CmdBuff().SetText(s, "") b.CmdBuff().SetText(s, "")
} }
func (b *Browser) SetLabelFilter(labels map[string]string) { func (b *Browser) SetLabelSelector(sel labels.Selector) {
b.CmdBuff().SetText(toLabelsStr(labels), "") if sel != nil {
b.GetModel().SetLabelFilter(toLabelsStr(labels)) b.CmdBuff().SetText(sel.String(), "")
}
b.GetModel().SetLabelSelector(sel)
} }
// BufferChanged indicates the buffer was changed. // BufferChanged indicates the buffer was changed.
@ -205,9 +208,11 @@ func (*Browser) BufferChanged(_, _ string) {}
// BufferCompleted indicates input was accepted. // BufferCompleted indicates input was accepted.
func (b *Browser) BufferCompleted(text, _ string) { func (b *Browser) BufferCompleted(text, _ string) {
if internal.IsLabelSelector(text) { if internal.IsLabelSelector(text) {
b.GetModel().SetLabelFilter(ui.TrimLabelSelector(text)) if sel, err := ui.TrimLabelSelector(text); err == nil {
b.GetModel().SetLabelSelector(sel)
}
} else { } else {
b.GetModel().SetLabelFilter("") b.GetModel().SetLabelSelector(labels.Everything())
} }
} }
@ -375,7 +380,7 @@ func (b *Browser) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
hasFilter := !b.CmdBuff().Empty() hasFilter := !b.CmdBuff().Empty()
b.CmdBuff().ClearText(false) b.CmdBuff().ClearText(false)
if hasFilter { if hasFilter {
b.GetModel().SetLabelFilter("") b.GetModel().SetLabelSelector(labels.Everything())
b.Refresh() b.Refresh()
} }
return b.App().PrevCmd(evt) return b.App().PrevCmd(evt)
@ -555,7 +560,9 @@ func (b *Browser) defaultContext() context.Context {
ctx = context.WithValue(ctx, internal.KeyGVR, b.GVR()) ctx = context.WithValue(ctx, internal.KeyGVR, b.GVR())
ctx = context.WithValue(ctx, internal.KeyPath, b.Path) ctx = context.WithValue(ctx, internal.KeyPath, b.Path)
if internal.IsLabelSelector(b.CmdBuff().GetText()) { if internal.IsLabelSelector(b.CmdBuff().GetText()) {
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(b.CmdBuff().GetText())) if sel, err := ui.TrimLabelSelector(b.CmdBuff().GetText()); err == nil {
ctx = context.WithValue(ctx, internal.KeyLabels, sel)
}
} }
ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(b.App().Config.ActiveNamespace())) ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(b.App().Config.ActiveNamespace()))
ctx = context.WithValue(ctx, internal.KeyWithMetrics, b.app.factory.Client().HasMetrics()) ctx = context.WithValue(ctx, internal.KeyWithMetrics, b.app.factory.Client().HasMetrics())

View File

@ -16,6 +16,7 @@ import (
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/k9s/internal/view/cmd"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
) )
@ -199,15 +200,15 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack, pushCmd bool)
co := c.componentFor(gvr, fqn, v) co := c.componentFor(gvr, fqn, v)
co.SetFilter("") co.SetFilter("")
co.SetLabelFilter(nil) co.SetLabelSelector(labels.Everything())
if f, ok := p.FilterArg(); ok { if f, ok := p.FilterArg(); ok {
co.SetFilter(f) co.SetFilter(f)
} }
if f, ok := p.FuzzyArg(); ok { if f, ok := p.FuzzyArg(); ok {
co.SetFilter("-f " + f) co.SetFilter("-f " + f)
} }
if ll, ok := p.LabelsArg(); ok { if ss, ok := p.LabelsArg(); ok {
co.SetLabelFilter(ll) co.SetLabelSelector(labels.SelectorFromSet(ss))
} }
return c.exec(p, gvr, co, clearStack, pushCmd) return c.exec(p, gvr, co, clearStack, pushCmd)

View File

@ -16,6 +16,7 @@ import (
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
"k8s.io/apimachinery/pkg/labels"
) )
const ( const (
@ -61,7 +62,7 @@ func NewDetails(app *App, title, subject, contentType string, searchable bool) *
func (*Details) SetCommand(*cmd.Interpreter) {} func (*Details) SetCommand(*cmd.Interpreter) {}
func (*Details) SetFilter(string) {} func (*Details) SetFilter(string) {}
func (*Details) SetLabelFilter(map[string]string) {} func (*Details) SetLabelSelector(labels.Selector) {}
// Init initializes the viewer. // Init initializes the viewer.
func (d *Details) Init(_ context.Context) error { func (d *Details) Init(_ context.Context) error {

View File

@ -90,5 +90,5 @@ func showPodsFromSelector(app *App, path string, sel *metav1.LabelSelector) {
return return
} }
showPods(app, path, l.String(), "") showPods(app, path, l, "")
} }

View File

@ -17,6 +17,7 @@ import (
"github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"k8s.io/apimachinery/pkg/labels"
) )
const ( const (
@ -46,7 +47,7 @@ func NewHelp(app *App) *Help {
func (*Help) SetCommand(*cmd.Interpreter) {} func (*Help) SetCommand(*cmd.Interpreter) {}
func (*Help) SetFilter(string) {} func (*Help) SetFilter(string) {}
func (*Help) SetLabelFilter(map[string]string) {} func (*Help) SetLabelSelector(labels.Selector) {}
// Init initializes the component. // Init initializes the component.
func (h *Help) Init(ctx context.Context) error { func (h *Help) Init(ctx context.Context) error {

View File

@ -23,11 +23,11 @@ import (
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
) )
@ -135,19 +135,10 @@ func describeResource(app *App, _ ui.Tabular, gvr *client.GVR, path string) {
} }
} }
func toLabelsStr(labels map[string]string) string { func showPods(app *App, path string, labelSel labels.Selector, fieldSel string) {
ll := make([]string, 0, len(labels))
for k, v := range labels {
ll = append(ll, fmt.Sprintf("%s=%s", k, v))
}
return strings.Join(ll, ",")
}
func showPods(app *App, path, labelSel, fieldSel string) {
v := NewPod(client.PodGVR) v := NewPod(client.PodGVR)
v.SetContextFn(podCtx(app, path, fieldSel)) v.SetContextFn(podCtx(app, path, fieldSel))
v.SetLabelFilter(cmd.ToLabels(labelSel)) v.SetLabelSelector(labelSel)
ns, _ := client.Namespaced(path) ns, _ := client.Namespaced(path)
if err := app.Config.SetActiveNamespace(ns); err != nil { if err := app.Config.SetActiveNamespace(ns); err != nil {

View File

@ -19,6 +19,7 @@ import (
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
"k8s.io/apimachinery/pkg/labels"
) )
const ( const (
@ -64,7 +65,7 @@ func NewLiveView(app *App, title string, m model.ResourceViewer) *LiveView {
func (*LiveView) SetCommand(*cmd.Interpreter) {} func (*LiveView) SetCommand(*cmd.Interpreter) {}
func (*LiveView) SetFilter(string) {} func (*LiveView) SetFilter(string) {}
func (*LiveView) SetLabelFilter(map[string]string) {} func (*LiveView) SetLabelSelector(labels.Selector) {}
// Init initializes the viewer. // Init initializes the viewer.
func (v *LiveView) Init(_ context.Context) error { func (v *LiveView) Init(_ context.Context) error {

View File

@ -25,6 +25,7 @@ import (
"github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"k8s.io/apimachinery/pkg/labels"
) )
const ( const (
@ -66,7 +67,7 @@ func NewLog(gvr *client.GVR, opts *dao.LogOptions) *Log {
func (*Log) SetCommand(*cmd.Interpreter) {} func (*Log) SetCommand(*cmd.Interpreter) {}
func (*Log) SetFilter(string) {} func (*Log) SetFilter(string) {}
func (*Log) SetLabelFilter(map[string]string) {} func (*Log) SetLabelSelector(labels.Selector) {}
// Init initializes the viewer. // Init initializes the viewer.
func (l *Log) Init(ctx context.Context) (err error) { func (l *Log) Init(ctx context.Context) (err error) {

View File

@ -92,7 +92,7 @@ func (n *Node) bindKeys(aa *ui.KeyActions) {
} }
func (n *Node) showPods(a *App, _ ui.Tabular, _ *client.GVR, path string) { func (n *Node) showPods(a *App, _ ui.Tabular, _ *client.GVR, path string) {
showPods(a, n.GetTable().GetSelectedItem(), client.BlankNamespace, "spec.nodeName="+path) showPods(a, n.GetTable().GetSelectedItem(), nil, "spec.nodeName="+path)
} }
func (n *Node) drainCmd(evt *tcell.EventKey) *tcell.EventKey { func (n *Node) drainCmd(evt *tcell.EventKey) *tcell.EventKey {

View File

@ -11,6 +11,7 @@ import (
"github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"k8s.io/apimachinery/pkg/labels"
) )
// Picker represents a container picker. // Picker represents a container picker.
@ -30,7 +31,7 @@ func NewPicker() *Picker {
func (*Picker) SetCommand(*cmd.Interpreter) {} func (*Picker) SetCommand(*cmd.Interpreter) {}
func (*Picker) SetFilter(string) {} func (*Picker) SetFilter(string) {}
func (*Picker) SetLabelFilter(map[string]string) {} func (*Picker) SetLabelSelector(labels.Selector) {}
// Init initializes the view. // Init initializes the view.
func (p *Picker) Init(ctx context.Context) error { func (p *Picker) Init(ctx context.Context) error {

View File

@ -22,6 +22,7 @@ import (
"github.com/derailed/tview" "github.com/derailed/tview"
"golang.org/x/text/cases" "golang.org/x/text/cases"
"golang.org/x/text/language" "golang.org/x/text/language"
"k8s.io/apimachinery/pkg/labels"
) )
// Graphable represents a graphic component. // Graphable represents a graphic component.
@ -80,7 +81,7 @@ func NewPulse(gvr *client.GVR) ResourceViewer {
func (*Pulse) SetCommand(*cmd.Interpreter) {} func (*Pulse) SetCommand(*cmd.Interpreter) {}
func (*Pulse) SetFilter(string) {} func (*Pulse) SetFilter(string) {}
func (*Pulse) SetLabelFilter(map[string]string) {} func (*Pulse) SetLabelSelector(labels.Selector) {}
// Init initializes the view. // Init initializes the view.
func (p *Pulse) Init(ctx context.Context) error { func (p *Pulse) Init(ctx context.Context) error {

View File

@ -13,6 +13,7 @@ import (
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/k9s/internal/ui/dialog"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// RestartExtender represents a restartable resource. // RestartExtender represents a restartable resource.
@ -54,22 +55,31 @@ func (r *RestartExtender) restartCmd(*tcell.EventKey) *tcell.EventKey {
msg = fmt.Sprintf("Restart %d %s?", len(paths), r.GVR().R()) msg = fmt.Sprintf("Restart %d %s?", len(paths), r.GVR().R())
} }
d := r.App().Styles.Dialog() d := r.App().Styles.Dialog()
dialog.ShowConfirm(&d, r.App().Content.Pages, "Confirm Restart", msg, func() {
ctx, cancel := context.WithTimeout(context.Background(), r.App().Conn().Config().CallTimeout()) opts := dialog.RestartDialogOpts{
defer cancel() Title: "Confirm Restart",
for _, path := range paths { Message: msg,
if err := r.restartRollout(ctx, path); err != nil { FieldManager: "kubectl-rollout",
r.App().Flash().Err(err) Ack: func(opts *metav1.PatchOptions) bool {
} else { ctx, cancel := context.WithTimeout(context.Background(), r.App().Conn().Config().CallTimeout())
r.App().Flash().Infof("Restart in progress for `%s...", path) defer cancel()
for _, path := range paths {
if err := r.restartRollout(ctx, path, opts); err != nil {
r.App().Flash().Err(err)
} else {
r.App().Flash().Infof("Restart in progress for `%s...", path)
}
} }
} return true
}, func() {}) },
Cancel: func() {},
}
dialog.ShowRestart(&d, r.App().Content.Pages, &opts)
return nil return nil
} }
func (r *RestartExtender) restartRollout(ctx context.Context, path string) error { func (r *RestartExtender) restartRollout(ctx context.Context, path string, opts *metav1.PatchOptions) error {
res, err := dao.AccessorFor(r.App().factory, r.GVR()) res, err := dao.AccessorFor(r.App().factory, r.GVR())
if err != nil { if err != nil {
return err return err
@ -79,7 +89,7 @@ func (r *RestartExtender) restartRollout(ctx context.Context, path string) error
return errors.New("resource is not restartable") return errors.New("resource is not restartable")
} }
return s.Restart(ctx, path) return s.Restart(ctx, path, opts)
} }
// Helpers... // Helpers...

View File

@ -74,7 +74,7 @@ func (s *Service) showPods(a *App, _ ui.Tabular, _ *client.GVR, path string) {
return return
} }
showPods(a, path, toLabelsStr(svc.Spec.Selector), "") showPods(a, path, labels.SelectorFromSet(svc.Spec.Selector), "")
} }
func (*Service) checkSvc(svc *v1.Service) error { func (*Service) checkSvc(svc *v1.Service) error {

View File

@ -24,6 +24,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -135,8 +136,8 @@ var _ ui.Tabular = (*mockTableModel)(nil)
func (*mockTableModel) SetViewSetting(context.Context, *config.ViewSetting) {} func (*mockTableModel) SetViewSetting(context.Context, *config.ViewSetting) {}
func (*mockTableModel) SetInstance(string) {} func (*mockTableModel) SetInstance(string) {}
func (*mockTableModel) SetLabelFilter(string) {} func (*mockTableModel) SetLabelSelector(labels.Selector) {}
func (*mockTableModel) GetLabelFilter() string { return "" } func (*mockTableModel) GetLabelSelector() labels.Selector { return nil }
func (*mockTableModel) Empty() bool { return false } func (*mockTableModel) Empty() bool { return false }
func (*mockTableModel) RowCount() int { return 1 } func (*mockTableModel) RowCount() int { return 1 }
func (*mockTableModel) HasMetrics() bool { return true } func (*mockTableModel) HasMetrics() bool { return true }

View File

@ -112,7 +112,9 @@ func (w *Workload) defaultContext(gvr *client.GVR, fqn string) context.Context {
ctx = context.WithValue(ctx, internal.KeyPath, fqn) ctx = context.WithValue(ctx, internal.KeyPath, fqn)
} }
if internal.IsLabelSelector(w.GetTable().CmdBuff().GetText()) { if internal.IsLabelSelector(w.GetTable().CmdBuff().GetText()) {
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(w.GetTable().CmdBuff().GetText())) if sel, err := ui.TrimLabelSelector(w.GetTable().CmdBuff().GetText()); err == nil {
ctx = context.WithValue(ctx, internal.KeyLabels, sel)
}
} }
ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(w.App().Config.ActiveNamespace())) ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(w.App().Config.ActiveNamespace()))
ctx = context.WithValue(ctx, internal.KeyWithMetrics, w.App().factory.Client().HasMetrics()) ctx = context.WithValue(ctx, internal.KeyWithMetrics, w.App().factory.Client().HasMetrics())

View File

@ -28,6 +28,7 @@ import (
"golang.org/x/text/cases" "golang.org/x/text/cases"
"golang.org/x/text/language" "golang.org/x/text/language"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
) )
@ -58,7 +59,7 @@ func NewXray(gvr *client.GVR) ResourceViewer {
func (*Xray) SetCommand(*cmd.Interpreter) {} func (*Xray) SetCommand(*cmd.Interpreter) {}
func (*Xray) SetFilter(string) {} func (*Xray) SetFilter(string) {}
func (*Xray) SetLabelFilter(map[string]string) {} func (*Xray) SetLabelSelector(labels.Selector) {}
// Init initializes the view. // Init initializes the view.
func (x *Xray) Init(ctx context.Context) error { func (x *Xray) Init(ctx context.Context) error {
@ -609,9 +610,11 @@ func (x *Xray) defaultContext() context.Context {
ctx := context.WithValue(context.Background(), internal.KeyFactory, x.app.factory) ctx := context.WithValue(context.Background(), internal.KeyFactory, x.app.factory)
ctx = context.WithValue(ctx, internal.KeyFields, "") ctx = context.WithValue(ctx, internal.KeyFields, "")
if x.CmdBuff().Empty() { if x.CmdBuff().Empty() {
ctx = context.WithValue(ctx, internal.KeyLabels, "") ctx = context.WithValue(ctx, internal.KeyLabels, labels.Everything())
} else { } else {
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(x.CmdBuff().GetText())) if sel, err := ui.TrimLabelSelector(x.CmdBuff().GetText()); err == nil {
ctx = context.WithValue(ctx, internal.KeyLabels, sel)
}
} }
return ctx return ctx
@ -688,7 +691,9 @@ func (x *Xray) styleTitle() string {
return title return title
} }
if internal.IsLabelSelector(buff) { if internal.IsLabelSelector(buff) {
buff = ui.TrimLabelSelector(buff) if sel, err := ui.TrimLabelSelector(buff); err == nil {
buff = sel.String()
}
} }
return title + ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), &styles) return title + ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), &styles)

View File

@ -14,18 +14,18 @@ import (
"github.com/anchore/clio" "github.com/anchore/clio"
"github.com/anchore/grype/cmd/grype/cli/options" "github.com/anchore/grype/cmd/grype/cli/options"
"github.com/anchore/grype/grype" "github.com/anchore/grype/grype"
"github.com/anchore/grype/grype/db/legacy/distribution" "github.com/anchore/grype/grype/match"
v5 "github.com/anchore/grype/grype/db/v5" "github.com/anchore/grype/grype/matcher"
"github.com/anchore/grype/grype/db/v5/matcher" "github.com/anchore/grype/grype/matcher/dotnet"
"github.com/anchore/grype/grype/db/v5/matcher/dotnet" "github.com/anchore/grype/grype/matcher/golang"
"github.com/anchore/grype/grype/db/v5/matcher/golang" "github.com/anchore/grype/grype/matcher/java"
"github.com/anchore/grype/grype/db/v5/matcher/java" "github.com/anchore/grype/grype/matcher/javascript"
"github.com/anchore/grype/grype/db/v5/matcher/javascript" "github.com/anchore/grype/grype/matcher/python"
"github.com/anchore/grype/grype/db/v5/matcher/python" "github.com/anchore/grype/grype/matcher/ruby"
"github.com/anchore/grype/grype/db/v5/matcher/ruby" "github.com/anchore/grype/grype/matcher/stock"
"github.com/anchore/grype/grype/db/v5/matcher/stock"
"github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/vex" "github.com/anchore/grype/grype/vex"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/syft/syft" "github.com/anchore/syft/syft"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
@ -40,8 +40,8 @@ const (
) )
type imageScanner struct { type imageScanner struct {
store *v5.ProviderStore provider vulnerability.Provider
dbStatus *distribution.Status status *vulnerability.ProviderStatus
opts *options.Grype opts *options.Grype
scans Scans scans Scans
mx sync.RWMutex mx sync.RWMutex
@ -90,8 +90,9 @@ func (s *imageScanner) Init(name, version string) {
s.opts.GenerateMissingCPEs = true s.opts.GenerateMissingCPEs = true
var err error var err error
s.store, s.dbStatus, err = grype.LoadVulnerabilityDB( s.provider, s.status, err = grype.LoadVulnerabilityDB(
s.opts.DB.ToLegacyCuratorConfig(), s.opts.ToClientConfig(),
s.opts.ToCuratorConfig(),
s.opts.DB.AutoUpdate, s.opts.DB.AutoUpdate,
) )
if err != nil { if err != nil {
@ -99,7 +100,7 @@ func (s *imageScanner) Init(name, version string) {
return return
} }
if e := validateDBLoad(err, s.dbStatus); e != nil { if e := validateDBLoad(err, s.status); e != nil {
s.log.Error("VulDb validate failed", slogs.Error, e) s.log.Error("VulDb validate failed", slogs.Error, e)
return return
} }
@ -112,9 +113,9 @@ func (s *imageScanner) Stop() {
s.mx.RLock() s.mx.RLock()
defer s.mx.RUnlock() defer s.mx.RUnlock()
if s.store != nil { if s.provider != nil {
_ = s.store.Close() _ = s.provider.Close()
s.store = nil s.provider = nil
} }
} }
@ -180,11 +181,11 @@ func (s *imageScanner) scan(_ context.Context, img string, sc *Scan) error {
} }
v := grype.VulnerabilityMatcher{ v := grype.VulnerabilityMatcher{
Store: *s.store, VulnerabilityProvider: s.provider,
IgnoreRules: s.opts.Ignore, IgnoreRules: s.opts.Ignore,
NormalizeByCVE: s.opts.ByCVE, NormalizeByCVE: s.opts.ByCVE,
FailSeverity: s.opts.FailOnSeverity(), FailSeverity: s.opts.FailOnSeverity(),
Matchers: getMatchers(s.opts), Matchers: getMatchers(s.opts),
VexProcessor: vex.NewProcessor(vex.ProcessorOptions{ VexProcessor: vex.NewProcessor(vex.ProcessorOptions{
Documents: s.opts.VexDocuments, Documents: s.opts.VexDocuments,
IgnoreRules: s.opts.Ignore, IgnoreRules: s.opts.Ignore,
@ -195,7 +196,7 @@ func (s *imageScanner) scan(_ context.Context, img string, sc *Scan) error {
if err != nil { if err != nil {
errs = errors.Join(errs, err) errs = errors.Join(errs, err)
} }
if err := sc.run(mm, s.store); err != nil { if err := sc.run(mm, s.provider); err != nil {
errs = errors.Join(errs, err) errs = errors.Join(errs, err)
} }
@ -218,7 +219,7 @@ func getProviderConfig(opts *options.Grype) pkg.ProviderConfig {
} }
} }
func getMatchers(opts *options.Grype) []matcher.Matcher { func getMatchers(opts *options.Grype) []match.Matcher {
return matcher.NewDefaultMatchers( return matcher.NewDefaultMatchers(
matcher.Config{ matcher.Config{
Java: java.MatcherConfig{ Java: java.MatcherConfig{
@ -230,23 +231,23 @@ func getMatchers(opts *options.Grype) []matcher.Matcher {
Dotnet: dotnet.MatcherConfig(opts.Match.Dotnet), Dotnet: dotnet.MatcherConfig(opts.Match.Dotnet),
Javascript: javascript.MatcherConfig(opts.Match.Javascript), Javascript: javascript.MatcherConfig(opts.Match.Javascript),
Golang: golang.MatcherConfig{ Golang: golang.MatcherConfig{
UseCPEs: opts.Match.Golang.UseCPEs, UseCPEs: opts.Match.Golang.UseCPEs,
AlwaysUseCPEForStdlib: opts.Match.Golang.AlwaysUseCPEForStdlib, AlwaysUseCPEForStdlib: opts.Match.Golang.AlwaysUseCPEForStdlib,
AllowMainModulePseudoVersionComparison: opts.Match.Golang.AllowMainModulePseudoVersionComparison,
}, },
Stock: stock.MatcherConfig(opts.Match.Stock), Stock: stock.MatcherConfig(opts.Match.Stock),
}, },
) )
} }
func validateDBLoad(loadErr error, status *vulnerability.ProviderStatus) error {
func validateDBLoad(loadErr error, status *distribution.Status) error {
if loadErr != nil { if loadErr != nil {
return fmt.Errorf("failed to load vulnerability db: %w", loadErr) return fmt.Errorf("failed to load vulnerability db: %w", loadErr)
} }
if status == nil { if status == nil {
return fmt.Errorf("unable to determine the status of the vulnerability db") return fmt.Errorf("unable to determine the status of the vulnerability db")
} }
if status.Err != nil { if status.Error != nil {
return fmt.Errorf("db could not be loaded: %w", status.Err) return fmt.Errorf("db could not be loaded: %w", status.Error)
} }
return nil return nil

View File

@ -1,6 +1,6 @@
name: k9s name: k9s
base: core22 base: core22
version: 'v0.50.3' version: 'v0.50.4'
summary: K9s is a CLI to view and manage your Kubernetes clusters. summary: K9s is a CLI to view and manage your Kubernetes clusters.
description: | 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. 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.