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 notesmine
parent
c2694ee3e5
commit
e4e3816185
|
|
@ -239,77 +239,3 @@ formatters:
|
|||
- internal/x # extracted from x/tools code
|
||||
- pkg/goformatters/gci/internal # extracted from gci 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
|
||||
2
Makefile
2
Makefile
|
|
@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
|
|||
else
|
||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||
endif
|
||||
VERSION ?= v0.50.3
|
||||
VERSION ?= v0.50.4
|
||||
IMG_NAME := derailed/k9s
|
||||
IMAGE := ${IMG_NAME}:${VERSION}
|
||||
|
||||
|
|
|
|||
|
|
@ -409,8 +409,10 @@ You can now override the context portForward default address configuration by se
|
|||
liveViewAutoRefresh: false
|
||||
# The path to screen dump. Default: '%temp_dir%/k9s-screens-%username%' (k9s info)
|
||||
screenDumpDir: /tmp/dumps
|
||||
# Represents ui poll intervals. Default 2secs
|
||||
# Represents ui poll intervals in seconds. Default 2secs
|
||||
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.
|
||||
maxConnRetry: 5
|
||||
# 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
|
||||
# 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
|
||||
# Toggles icons display as not all terminal support these chars. Default: true
|
||||
noIcons: false
|
||||
# Toggles reactive UI. This option provide for watching on disk artifacts changes and update the UI live Defaults to false.
|
||||
reactive: false
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
11
cmd/root.go
11
cmd/root.go
|
|
@ -12,18 +12,17 @@ import (
|
|||
"strings"
|
||||
"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/color"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
"github.com/derailed/k9s/internal/slogs"
|
||||
"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 (
|
||||
|
|
|
|||
9
go.mod
9
go.mod
|
|
@ -5,7 +5,7 @@ go 1.24.1
|
|||
require (
|
||||
github.com/adrg/xdg v0.5.3
|
||||
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/atotto/clipboard v0.1.4
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
|
|
@ -152,7 +152,7 @@ require (
|
|||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gdamore/encoding v1.0.1 // 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/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // 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-testing-interface v1.14.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/moby/docker-image-spec v1.3.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/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 // 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/pelletier/go-toml v1.9.5 // 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/sys v0.31.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/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/api v0.215.0 // indirect
|
||||
|
|
|
|||
18
go.sum
18
go.sum
|
|
@ -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-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/grype v0.87.0 h1:cdrNA4rnSMpBxP8NmKNF75VUlP/VWaOG8YJJjiIDwIs=
|
||||
github.com/anchore/grype v0.87.0/go.mod h1:Umw/9sZHnS+9mPLTnUFd/OzApR6dzE2CPlRsov7kSmQ=
|
||||
github.com/anchore/grype v0.91.0 h1:x6/jweLDNp+jy6ufyCukBJbFAlVefxSUOqb2eFsJdQY=
|
||||
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/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
|
||||
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-snaps v0.5.11 h1:LFG0ggUKR+KEiiaOvFCmLgJ5NO2zf93AxxddkBn3LdQ=
|
||||
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.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
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/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
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-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
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 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.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/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
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/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/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.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
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.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.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ package client
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/slogs"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
|
@ -19,7 +21,8 @@ import (
|
|||
)
|
||||
|
||||
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 = true
|
||||
|
|
@ -42,12 +45,13 @@ func NewConfig(f *genericclioptions.ConfigFlags) *Config {
|
|||
// CallTimeout returns the call timeout if set or the default if not set.
|
||||
func (c *Config) CallTimeout() time.Duration {
|
||||
if !isSet(c.flags.Timeout) {
|
||||
return defaultCallTimeoutDuration
|
||||
return DefaultCallTimeoutDuration
|
||||
}
|
||||
dur, err := time.ParseDuration(*c.flags.Timeout)
|
||||
if err != nil {
|
||||
return defaultCallTimeoutDuration
|
||||
return DefaultCallTimeoutDuration
|
||||
}
|
||||
slog.Debug("APIServer timeout", slogs.Duration, dur)
|
||||
|
||||
return dur
|
||||
}
|
||||
|
|
@ -95,6 +99,8 @@ func (c *Config) SwitchContext(name string) error {
|
|||
flags.ImpersonateGroup = c.flags.ImpersonateGroup
|
||||
flags.ImpersonateUID = c.flags.ImpersonateUID
|
||||
flags.Insecure = c.flags.Insecure
|
||||
flags.BearerToken = c.flags.BearerToken
|
||||
|
||||
c.flags = flags
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func TestCallTimeout(t *testing.T) {
|
|||
e: 1 * time.Minute,
|
||||
},
|
||||
"default": {
|
||||
e: 10 * time.Second,
|
||||
e: 15 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var NoGVR = &GVR{}
|
||||
var NoGVR = new(GVR)
|
||||
|
||||
// GVR represents a kubernetes resource schema as a string.
|
||||
// Format is group/version/resources:subresource.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ var (
|
|||
ScGVR = NewGVR("storage.k8s.io/v1/storageclasses")
|
||||
|
||||
// Policy...
|
||||
PdbGVR = NewGVR("policy/v1/PodDisruptionBudgets")
|
||||
PdbGVR = NewGVR("policy/v1/poddisruptionbudgets")
|
||||
PspGVR = NewGVR("policy/v1beta1/podsecuritypolicies")
|
||||
|
||||
// Metrics...
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"io/fs"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"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
|
||||
}
|
||||
|
||||
func setK8sTimeout(flags *genericclioptions.ConfigFlags, d time.Duration) {
|
||||
v := d.String()
|
||||
flags.Timeout = &v
|
||||
}
|
||||
|
||||
// Refine the configuration based on cli args.
|
||||
func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, cfg *client.Config) error {
|
||||
if flags == 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 _, err := c.K9s.ActivateContext(*flags.Context); err != nil {
|
||||
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()
|
||||
case isStringSet(flags.Namespace):
|
||||
ns = *flags.Namespace
|
||||
c.ResetActiveView()
|
||||
default:
|
||||
nss, err := c.K9s.ActiveContextNamespace()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -49,9 +49,8 @@ func TestConfigSave(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
for k, u := range uu {
|
||||
xdg.Reload()
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
c := mock.NewMockConfig(t)
|
||||
_, 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))
|
||||
|
||||
cfg.K9s.RefreshRate = 100
|
||||
cfg.K9s.APIServerTimeout = "30s"
|
||||
cfg.K9s.ReadOnly = true
|
||||
cfg.K9s.Logger.TailCount = 500
|
||||
cfg.K9s.Logger.BufferSize = 800
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"liveViewAutoRefresh": { "type": "boolean" },
|
||||
"screenDumpDir": {"type": "string"},
|
||||
"refreshRate": { "type": "integer" },
|
||||
"apiServerTimeout": { "type": "string" },
|
||||
"maxConnRetry": { "type": "integer" },
|
||||
"readOnly": { "type": "boolean" },
|
||||
"noExitOnCtrlC": { "type": "boolean" },
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ type K9s struct {
|
|||
LiveViewAutoRefresh bool `json:"liveViewAutoRefresh" yaml:"liveViewAutoRefresh"`
|
||||
ScreenDumpDir string `json:"screenDumpDir" yaml:"screenDumpDir,omitempty"`
|
||||
RefreshRate int `json:"refreshRate" yaml:"refreshRate"`
|
||||
APIServerTimeout string `json:"apiServerTimeout" yaml:"apiServerTimeout"`
|
||||
MaxConnRetry int32 `json:"maxConnRetry" yaml:"maxConnRetry"`
|
||||
ReadOnly bool `json:"readOnly" yaml:"readOnly"`
|
||||
NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"`
|
||||
|
|
@ -54,6 +55,7 @@ func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s {
|
|||
return &K9s{
|
||||
RefreshRate: defaultRefreshRate,
|
||||
MaxConnRetry: defaultMaxConnRetry,
|
||||
APIServerTimeout: client.DefaultCallTimeoutDuration.String(),
|
||||
ScreenDumpDir: AppDumpsDir,
|
||||
Logger: NewLogger(),
|
||||
Thresholds: NewThreshold(),
|
||||
|
|
@ -117,9 +119,11 @@ func (k *K9s) Merge(k1 *K9s) {
|
|||
k.DefaultView = k1.DefaultView
|
||||
k.ScreenDumpDir = k1.ScreenDumpDir
|
||||
k.RefreshRate = k1.RefreshRate
|
||||
k.APIServerTimeout = k1.APIServerTimeout
|
||||
k.MaxConnRetry = k1.MaxConnRetry
|
||||
k.ReadOnly = k1.ReadOnly
|
||||
k.NoExitOnCtrlC = k1.NoExitOnCtrlC
|
||||
k.PortForwardAddress = k1.PortForwardAddress
|
||||
k.UI = k1.UI
|
||||
k.SkipLatestRevCheck = k1.SkipLatestRevCheck
|
||||
k.DisablePodCounting = k1.DisablePodCounting
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const (
|
|||
DefaultLoggerTailCount = 100
|
||||
|
||||
// MaxLogThreshold sets the max value for log size.
|
||||
MaxLogThreshold = 5000
|
||||
MaxLogThreshold = 5_000
|
||||
|
||||
// DefaultSinceSeconds tracks default log age.
|
||||
DefaultSinceSeconds = -1 // tail logs by default
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ k9s:
|
|||
liveViewAutoRefresh: false
|
||||
screenDumpDir: /tmp/k9s-test/screen-dumps
|
||||
refreshRate: 2
|
||||
apiServerTimeout: 15s
|
||||
maxConnRetry: 5
|
||||
readOnly: false
|
||||
noExitOnCtrlC: false
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ k9s:
|
|||
liveViewAutoRefresh: true
|
||||
screenDumpDir: /tmp/k9s-test/screen-dumps
|
||||
refreshRate: 100
|
||||
apiServerTimeout: 30s
|
||||
maxConnRetry: 5
|
||||
readOnly: true
|
||||
noExitOnCtrlC: false
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ k9s:
|
|||
liveViewAutoRefresh: true
|
||||
screenDumpDir: /tmp/k9s-test/screen-dumps
|
||||
refreshRate: 2
|
||||
apiServerTimeout: 10s
|
||||
maxConnRetry: 5
|
||||
readOnly: false
|
||||
noExitOnCtrlC: false
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) err
|
|||
}
|
||||
|
||||
// Restart a Deployment rollout.
|
||||
func (d *Deployment) Restart(ctx context.Context, path string) error {
|
||||
return restartRes[*appsv1.Deployment](ctx, d.getFactory(), client.DpGVR, path)
|
||||
func (d *Deployment) Restart(ctx context.Context, path string, opts *metav1.PatchOptions) error {
|
||||
return restartRes[*appsv1.Deployment](ctx, d.getFactory(), client.DpGVR, path, opts)
|
||||
}
|
||||
|
||||
// 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())
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -440,7 +440,7 @@ func restartRes[T runtime.Object](ctx context.Context, f Factory, gvr *client.GV
|
|||
n,
|
||||
types.StrategicMergePatchType,
|
||||
diff,
|
||||
metav1.PatchOptions{},
|
||||
*opts,
|
||||
)
|
||||
|
||||
case client.DsGVR:
|
||||
|
|
@ -449,7 +449,7 @@ func restartRes[T runtime.Object](ctx context.Context, f Factory, gvr *client.GV
|
|||
n,
|
||||
types.StrategicMergePatchType,
|
||||
diff,
|
||||
metav1.PatchOptions{},
|
||||
*opts,
|
||||
)
|
||||
|
||||
case client.StsGVR:
|
||||
|
|
@ -458,7 +458,7 @@ func restartRes[T runtime.Object](ctx context.Context, f Factory, gvr *client.GV
|
|||
n,
|
||||
types.StrategicMergePatchType,
|
||||
diff,
|
||||
metav1.PatchOptions{},
|
||||
*opts,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ func (d *DaemonSet) ListImages(_ context.Context, fqn string) ([]string, error)
|
|||
}
|
||||
|
||||
// Restart a DaemonSet rollout.
|
||||
func (d *DaemonSet) Restart(ctx context.Context, path string) error {
|
||||
return restartRes[*appsv1.DaemonSet](ctx, d.getFactory(), client.DsGVR, path)
|
||||
func (d *DaemonSet) Restart(ctx context.Context, path string, opts *metav1.PatchOptions) error {
|
||||
return restartRes[*appsv1.DaemonSet](ctx, d.getFactory(), client.DsGVR, path, opts)
|
||||
}
|
||||
|
||||
// TailLogs tail logs for all pods represented by this DaemonSet.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"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) {
|
||||
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()}
|
||||
ns, n := client.Namespaced(fqn)
|
||||
if n != "" {
|
||||
|
|
@ -51,7 +56,7 @@ func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, er
|
|||
b := f.NewBuilder().
|
||||
Unstructured().
|
||||
NamespaceParam(ns).DefaultNamespace().AllNamespaces(allNS).
|
||||
LabelSelectorParam(strLabel).
|
||||
LabelSelectorParam(sel.String()).
|
||||
FieldSelectorParam("").
|
||||
RequestChunksOf(0).
|
||||
ResourceTypeOrNameArgs(true, opts...).
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/client"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
|
@ -38,7 +39,10 @@ type Generic struct {
|
|||
// List returns a collection of resources.
|
||||
// BOZO!! no auth check??
|
||||
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) {
|
||||
ns = client.BlankNamespace
|
||||
}
|
||||
|
|
@ -48,11 +52,12 @@ func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
opts := metav1.ListOptions{LabelSelector: labelSel.String()}
|
||||
var ll *unstructured.UnstructuredList
|
||||
if client.IsClusterScoped(ns) {
|
||||
ll, err = dial.List(ctx, metav1.ListOptions{LabelSelector: labelSel})
|
||||
ll, err = dial.List(ctx, opts)
|
||||
} else {
|
||||
ll, err = dial.Namespace(ns).List(ctx, metav1.ListOptions{LabelSelector: labelSel})
|
||||
ll, err = dial.Namespace(ns).List(ctx, opts)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -25,12 +25,9 @@ type Resource struct {
|
|||
|
||||
// List returns a collection of resources.
|
||||
func (r *Resource) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
strLabel, _ := ctx.Value(internal.KeyLabels).(string)
|
||||
lsel := labels.Everything()
|
||||
if strLabel != "" {
|
||||
if sel, err := labels.Parse(strLabel); err == nil {
|
||||
lsel = sel
|
||||
}
|
||||
if sel, ok := ctx.Value(internal.KeyLabels).(labels.Selector); ok {
|
||||
lsel = sel
|
||||
}
|
||||
|
||||
return r.getFactory().List(r.gvr, ns, false, lsel)
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ func (s *StatefulSet) Scale(ctx context.Context, path string, replicas int32) er
|
|||
}
|
||||
|
||||
// Restart a StatefulSet rollout.
|
||||
func (s *StatefulSet) Restart(ctx context.Context, path string) error {
|
||||
return restartRes[*appsv1.StatefulSet](ctx, s.getFactory(), client.StsGVR, path)
|
||||
func (s *StatefulSet) Restart(ctx context.Context, path string, opts *metav1.PatchOptions) error {
|
||||
return restartRes[*appsv1.StatefulSet](ctx, s.getFactory(), client.StsGVR, path, opts)
|
||||
}
|
||||
|
||||
// GetInstance returns a statefulset instance.
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"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.
|
||||
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)
|
||||
|
||||
includeObject := includeMeta
|
||||
|
|
@ -75,7 +79,7 @@ func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
Namespace(ns).
|
||||
Resource(t.gvr.R()).
|
||||
VersionedParams(&metav1.ListOptions{
|
||||
LabelSelector: labelSel,
|
||||
LabelSelector: sel.String(),
|
||||
FieldSelector: fieldSel,
|
||||
}, metav1.ParameterCodec).
|
||||
Do(ctx).Get()
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ type Switchable interface {
|
|||
// Restartable represents a restartable resource.
|
||||
type Restartable interface {
|
||||
// Restart performs a rollout restart.
|
||||
Restart(ctx context.Context, path string) error
|
||||
Restart(context.Context, string, *metav1.PatchOptions) error
|
||||
}
|
||||
|
||||
// Runnable represents a runnable resource.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/derailed/tcell/v2"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -313,4 +314,4 @@ func (c) Start() {}
|
|||
func (c) Stop() {}
|
||||
func (c) Init(context.Context) error { return nil }
|
||||
func (c) SetFilter(string) {}
|
||||
func (c) SetLabelFilter(map[string]string) {}
|
||||
func (c) SetLabelSelector(labels.Selector) {}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/model1"
|
||||
"github.com/derailed/k9s/internal/slogs"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
@ -38,15 +39,15 @@ type TableListener interface {
|
|||
|
||||
// Table represents a table model.
|
||||
type Table struct {
|
||||
gvr *client.GVR
|
||||
data *model1.TableData
|
||||
listeners []TableListener
|
||||
inUpdate int32
|
||||
refreshRate time.Duration
|
||||
instance string
|
||||
labelFilter string
|
||||
mx sync.RWMutex
|
||||
vs *config.ViewSetting
|
||||
gvr *client.GVR
|
||||
data *model1.TableData
|
||||
listeners []TableListener
|
||||
inUpdate int32
|
||||
refreshRate time.Duration
|
||||
instance string
|
||||
labelSelector labels.Selector
|
||||
mx sync.RWMutex
|
||||
vs *config.ViewSetting
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (t *Table) SetLabelFilter(f string) {
|
||||
// SetLabelSelector sets the labels selector.
|
||||
func (t *Table) SetLabelSelector(sel labels.Selector) {
|
||||
t.mx.Lock()
|
||||
defer t.mx.Unlock()
|
||||
|
||||
t.labelFilter = f
|
||||
t.labelSelector = sel
|
||||
}
|
||||
|
||||
// GetLabelFilter sets the labels filter.
|
||||
func (t *Table) GetLabelFilter() string {
|
||||
// GetLabelSelector sets the labels selector.
|
||||
func (t *Table) GetLabelSelector() labels.Selector {
|
||||
t.mx.Lock()
|
||||
defer t.mx.Unlock()
|
||||
|
||||
return t.labelFilter
|
||||
return t.labelSelector
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
t.mx.RLock()
|
||||
ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter)
|
||||
ctx = context.WithValue(ctx, internal.KeyLabels, t.labelSelector)
|
||||
t.mx.RUnlock()
|
||||
|
||||
ns := client.CleanseNamespace(t.data.GetNamespace())
|
||||
|
|
@ -273,7 +274,7 @@ func (t *Table) reconcile(ctx context.Context) error {
|
|||
if t.vs != nil {
|
||||
meta.DAO.SetIncludeObject(true)
|
||||
}
|
||||
ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter)
|
||||
ctx = context.WithValue(ctx, internal.KeyLabels, t.labelSelector)
|
||||
if t.instance == "" {
|
||||
oo, err = t.list(ctx, meta.DAO)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/view/cmd"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/sahilm/fuzzy"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
@ -105,7 +106,7 @@ type Viewer interface {
|
|||
|
||||
type Filterer interface {
|
||||
SetFilter(string)
|
||||
SetLabelFilter(map[string]string)
|
||||
SetLabelSelector(labels.Selector)
|
||||
}
|
||||
|
||||
// Cruder performs crud operations.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import (
|
|||
"log/slog"
|
||||
"reflect"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/slogs"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
const ageCol = "AGE"
|
||||
|
|
@ -171,6 +173,24 @@ func (h Header) Diff(header Header) bool {
|
|||
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
|
||||
func (h Header) ColumnNames(wide bool) []string {
|
||||
if len(h) == 0 {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,10 @@ func (t *TableData) Filter(f FilterOpts) *TableData {
|
|||
}
|
||||
|
||||
func (t *TableData) rxFilter(q string, inverse bool) (*RowEvents, error) {
|
||||
if strings.Contains(q, " ") {
|
||||
return t.rowEvents, nil
|
||||
}
|
||||
|
||||
if inverse {
|
||||
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)
|
||||
}
|
||||
|
||||
var startIndex int
|
||||
if _, ok := t.header.IndexOf("NAMESPACE", true); ok && client.IsNamespaced(t.namespace) {
|
||||
startIndex = 1
|
||||
}
|
||||
vidx := t.header.FilterColIndices(t.namespace, true)
|
||||
rr := NewRowEvents(t.RowCount() / 2)
|
||||
ageIndex, _ := t.header.IndexOf("AGE", true)
|
||||
t.rowEvents.Range(func(_ int, re RowEvent) bool {
|
||||
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)
|
||||
}
|
||||
if ageIndex >= 0 && startIndex != ageIndex && ageIndex+1 <= len(ff) {
|
||||
ff = append(ff[0:ageIndex], ff[ageIndex+1:]...)
|
||||
}
|
||||
match := rx.MatchString(strings.Join(ff, spacer))
|
||||
if (inverse && !match) || (!inverse && match) {
|
||||
rr.Add(re)
|
||||
|
|
|
|||
|
|
@ -87,16 +87,20 @@ func (d Deployment) defaultRow(raw *unstructured.Unstructured, r *model1.Row) er
|
|||
return err
|
||||
}
|
||||
|
||||
var desired int32
|
||||
if dp.Spec.Replicas != nil {
|
||||
desired = *dp.Spec.Replicas
|
||||
}
|
||||
r.ID = client.MetaFQN(&dp.ObjectMeta)
|
||||
r.Fields = model1.Fields{
|
||||
dp.Namespace,
|
||||
dp.Name,
|
||||
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.AvailableReplicas)),
|
||||
mapToStr(dp.Labels),
|
||||
AsStatus(d.diagnose(dp.Status.Replicas, dp.Status.AvailableReplicas)),
|
||||
AsStatus(d.diagnose(desired, dp.Status.AvailableReplicas)),
|
||||
ToAge(dp.GetCreationTimestamp()),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -215,4 +215,7 @@ const (
|
|||
|
||||
// JQExp tracks a jq expression logger key.
|
||||
JQExp = "jq-exp"
|
||||
|
||||
// Duration tracks a duration logger key.
|
||||
Duration = "duration"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/derailed/tcell/v2"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -60,4 +61,4 @@ func (c) Start() {}
|
|||
func (c) Stop() {}
|
||||
func (c) Init(context.Context) error { return nil }
|
||||
func (c) SetFilter(string) {}
|
||||
func (c) SetLabelFilter(map[string]string) {}
|
||||
func (c) SetLabelSelector(labels.Selector) {}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -315,12 +315,18 @@ func (t *Table) doUpdate(data *model1.TableData) *model1.TableData {
|
|||
} else {
|
||||
t.actions.Delete(KeyShiftP)
|
||||
}
|
||||
|
||||
t.setSortCol(data.ComputeSortCol(t.GetViewSetting(), t.getSortCol(), t.getMSort()))
|
||||
|
||||
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) {
|
||||
t.Clear()
|
||||
fg := t.styles.Table().Header.FgColor.Color()
|
||||
|
|
@ -328,19 +334,9 @@ func (t *Table) UpdateUI(cdata, data *model1.TableData) {
|
|||
|
||||
var col int
|
||||
for _, h := range cdata.Header() {
|
||||
if h.Hide || (!t.wide && h.Wide) {
|
||||
if t.shouldExcludeColumn(h) {
|
||||
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)
|
||||
c := t.GetCell(0, col)
|
||||
c.SetBackgroundColor(bg)
|
||||
|
|
@ -384,17 +380,7 @@ func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads M
|
|||
)
|
||||
continue
|
||||
}
|
||||
if h[c].Hide || (!t.wide && h[c].Wide) {
|
||||
continue
|
||||
}
|
||||
|
||||
if h[c].Name == "NAMESPACE" && !t.GetModel().ClusterWide() {
|
||||
continue
|
||||
}
|
||||
if h[c].MX && !t.hasMetrics {
|
||||
continue
|
||||
}
|
||||
if h[c].VS && vul.ImgScanner == nil {
|
||||
if t.shouldExcludeColumn(h[c]) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -468,7 +454,6 @@ func (t *Table) Refresh() {
|
|||
if data.HeaderCount() == 0 {
|
||||
return
|
||||
}
|
||||
// BOZO!! Really want to tell model reload now. Refactor!
|
||||
cdata := t.Update(data, t.hasMetrics)
|
||||
t.UpdateUI(cdata, data)
|
||||
}
|
||||
|
|
@ -575,9 +560,14 @@ func (t *Table) styleTitle() string {
|
|||
|
||||
buff := t.cmdBuff.GetText()
|
||||
if internal.IsLabelSelector(buff) {
|
||||
buff = render.Truncate(TrimLabelSelector(buff), maxTruncate)
|
||||
} else if l := t.GetModel().GetLabelFilter(); l != "" {
|
||||
buff = render.Truncate(l, maxTruncate)
|
||||
sel, err := TrimLabelSelector(buff)
|
||||
if err != nil {
|
||||
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 == "" {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/slogs"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -58,12 +59,13 @@ func TrimCell(tv *SelectTable, row, col int) string {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
return strings.TrimSpace(s[2:])
|
||||
selStr = strings.TrimSpace(s[2:])
|
||||
}
|
||||
|
||||
return s
|
||||
return labels.Parse(selStr)
|
||||
}
|
||||
|
||||
// SkinTitle decorates a title.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
func TestTruncate(t *testing.T) {
|
||||
|
|
@ -34,17 +35,29 @@ func TestTruncate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTrimLabelSelector(t *testing.T) {
|
||||
sel, _ := labels.Parse("app=fred,env=blee")
|
||||
uu := map[string]struct {
|
||||
sel, e string
|
||||
sel string
|
||||
err error
|
||||
e labels.Selector
|
||||
}{
|
||||
"cool": {"-l app=fred,env=blee", "app=fred,env=blee"},
|
||||
"noSpace": {"-lapp=fred,env=blee", "app=fred,env=blee"},
|
||||
"cool": {
|
||||
sel: "-l app=fred,env=blee",
|
||||
e: sel,
|
||||
},
|
||||
|
||||
"no-space": {
|
||||
sel: "-lapp=fred,env=blee",
|
||||
e: sel,
|
||||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ func TestTableUpdate(t *testing.T) {
|
|||
func TestTableSelection(t *testing.T) {
|
||||
v := ui.NewTable(client.NewGVR("fred"))
|
||||
v.Init(makeContext())
|
||||
m := &mockModel{}
|
||||
m := new(mockModel)
|
||||
v.SetModel(m)
|
||||
data := m.Peek()
|
||||
cdata := v.Update(data, false)
|
||||
|
|
@ -72,8 +73,8 @@ var _ ui.Tabular = &mockModel{}
|
|||
|
||||
func (*mockModel) SetViewSetting(context.Context, *config.ViewSetting) {}
|
||||
func (*mockModel) SetInstance(string) {}
|
||||
func (*mockModel) SetLabelFilter(string) {}
|
||||
func (*mockModel) GetLabelFilter() string { return "" }
|
||||
func (*mockModel) SetLabelSelector(labels.Selector) {}
|
||||
func (*mockModel) GetLabelSelector() labels.Selector { return nil }
|
||||
func (*mockModel) Empty() bool { return false }
|
||||
func (*mockModel) RowCount() int { return 1 }
|
||||
func (*mockModel) HasMetrics() bool { return true }
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/model1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
@ -49,11 +50,11 @@ type Tabular interface {
|
|||
// SetInstance sets parent resource path.
|
||||
SetInstance(string)
|
||||
|
||||
// SetLabelFilter sets the label filter.
|
||||
SetLabelFilter(string)
|
||||
// SetLabelSelector sets the label selector.
|
||||
SetLabelSelector(labels.Selector)
|
||||
|
||||
// GetLabelFilter fetch the label filter.
|
||||
GetLabelFilter() string
|
||||
// GetLabelSelector fetch the label filter.
|
||||
GetLabelSelector() labels.Selector
|
||||
|
||||
// Empty returns true if model has no data.
|
||||
Empty() bool
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ func TestAliasNew(t *testing.T) {
|
|||
func TestAliasSearch(t *testing.T) {
|
||||
v := view.NewAlias(client.AliGVR)
|
||||
require.NoError(t, v.Init(makeContext(t)))
|
||||
v.GetTable().SetModel(&mockModel{})
|
||||
v.GetTable().SetModel(new(mockModel))
|
||||
v.GetTable().Refresh()
|
||||
v.App().Prompt().SetModel(v.GetTable().CmdBuff())
|
||||
v.App().Prompt().SendStrokes("blee")
|
||||
|
|
@ -93,8 +94,8 @@ func (*mockModel) NextSuggestion() (string, bool) { return
|
|||
func (*mockModel) PrevSuggestion() (string, bool) { return "", false }
|
||||
func (*mockModel) ClearSuggestions() {}
|
||||
func (*mockModel) SetInstance(string) {}
|
||||
func (*mockModel) SetLabelFilter(string) {}
|
||||
func (*mockModel) GetLabelFilter() string { return "" }
|
||||
func (*mockModel) SetLabelSelector(labels.Selector) {}
|
||||
func (*mockModel) GetLabelSelector() labels.Selector { return nil }
|
||||
func (*mockModel) Empty() bool { return false }
|
||||
func (*mockModel) RowCount() int { return 1 }
|
||||
func (*mockModel) HasMetrics() bool { return true }
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/view/cmd"
|
||||
"github.com/derailed/tcell/v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
|
|
@ -194,9 +195,11 @@ func (b *Browser) SetFilter(s string) {
|
|||
b.CmdBuff().SetText(s, "")
|
||||
}
|
||||
|
||||
func (b *Browser) SetLabelFilter(labels map[string]string) {
|
||||
b.CmdBuff().SetText(toLabelsStr(labels), "")
|
||||
b.GetModel().SetLabelFilter(toLabelsStr(labels))
|
||||
func (b *Browser) SetLabelSelector(sel labels.Selector) {
|
||||
if sel != nil {
|
||||
b.CmdBuff().SetText(sel.String(), "")
|
||||
}
|
||||
b.GetModel().SetLabelSelector(sel)
|
||||
}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
|
|
@ -205,9 +208,11 @@ func (*Browser) BufferChanged(_, _ string) {}
|
|||
// BufferCompleted indicates input was accepted.
|
||||
func (b *Browser) BufferCompleted(text, _ string) {
|
||||
if internal.IsLabelSelector(text) {
|
||||
b.GetModel().SetLabelFilter(ui.TrimLabelSelector(text))
|
||||
if sel, err := ui.TrimLabelSelector(text); err == nil {
|
||||
b.GetModel().SetLabelSelector(sel)
|
||||
}
|
||||
} 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()
|
||||
b.CmdBuff().ClearText(false)
|
||||
if hasFilter {
|
||||
b.GetModel().SetLabelFilter("")
|
||||
b.GetModel().SetLabelSelector(labels.Everything())
|
||||
b.Refresh()
|
||||
}
|
||||
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.KeyPath, b.Path)
|
||||
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.KeyWithMetrics, b.app.factory.Client().HasMetrics())
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/slogs"
|
||||
"github.com/derailed/k9s/internal/view/cmd"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"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.SetFilter("")
|
||||
co.SetLabelFilter(nil)
|
||||
co.SetLabelSelector(labels.Everything())
|
||||
if f, ok := p.FilterArg(); ok {
|
||||
co.SetFilter(f)
|
||||
}
|
||||
if f, ok := p.FuzzyArg(); ok {
|
||||
co.SetFilter("-f " + f)
|
||||
}
|
||||
if ll, ok := p.LabelsArg(); ok {
|
||||
co.SetLabelFilter(ll)
|
||||
if ss, ok := p.LabelsArg(); ok {
|
||||
co.SetLabelSelector(labels.SelectorFromSet(ss))
|
||||
}
|
||||
|
||||
return c.exec(p, gvr, co, clearStack, pushCmd)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/derailed/tcell/v2"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/sahilm/fuzzy"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -61,7 +62,7 @@ func NewDetails(app *App, title, subject, contentType string, searchable bool) *
|
|||
|
||||
func (*Details) SetCommand(*cmd.Interpreter) {}
|
||||
func (*Details) SetFilter(string) {}
|
||||
func (*Details) SetLabelFilter(map[string]string) {}
|
||||
func (*Details) SetLabelSelector(labels.Selector) {}
|
||||
|
||||
// Init initializes the viewer.
|
||||
func (d *Details) Init(_ context.Context) error {
|
||||
|
|
|
|||
|
|
@ -90,5 +90,5 @@ func showPodsFromSelector(app *App, path string, sel *metav1.LabelSelector) {
|
|||
return
|
||||
}
|
||||
|
||||
showPods(app, path, l.String(), "")
|
||||
showPods(app, path, l, "")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/view/cmd"
|
||||
"github.com/derailed/tcell/v2"
|
||||
"github.com/derailed/tview"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -46,7 +47,7 @@ func NewHelp(app *App) *Help {
|
|||
|
||||
func (*Help) SetCommand(*cmd.Interpreter) {}
|
||||
func (*Help) SetFilter(string) {}
|
||||
func (*Help) SetLabelFilter(map[string]string) {}
|
||||
func (*Help) SetLabelSelector(labels.Selector) {}
|
||||
|
||||
// Init initializes the component.
|
||||
func (h *Help) Init(ctx context.Context) error {
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ import (
|
|||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/slogs"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/k9s/internal/view/cmd"
|
||||
"github.com/derailed/tcell/v2"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/sahilm/fuzzy"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"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 {
|
||||
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) {
|
||||
func showPods(app *App, path string, labelSel labels.Selector, fieldSel string) {
|
||||
v := NewPod(client.PodGVR)
|
||||
v.SetContextFn(podCtx(app, path, fieldSel))
|
||||
v.SetLabelFilter(cmd.ToLabels(labelSel))
|
||||
v.SetLabelSelector(labelSel)
|
||||
|
||||
ns, _ := client.Namespaced(path)
|
||||
if err := app.Config.SetActiveNamespace(ns); err != nil {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/derailed/tcell/v2"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/sahilm/fuzzy"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -64,7 +65,7 @@ func NewLiveView(app *App, title string, m model.ResourceViewer) *LiveView {
|
|||
|
||||
func (*LiveView) SetCommand(*cmd.Interpreter) {}
|
||||
func (*LiveView) SetFilter(string) {}
|
||||
func (*LiveView) SetLabelFilter(map[string]string) {}
|
||||
func (*LiveView) SetLabelSelector(labels.Selector) {}
|
||||
|
||||
// Init initializes the viewer.
|
||||
func (v *LiveView) Init(_ context.Context) error {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/view/cmd"
|
||||
"github.com/derailed/tcell/v2"
|
||||
"github.com/derailed/tview"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -66,7 +67,7 @@ func NewLog(gvr *client.GVR, opts *dao.LogOptions) *Log {
|
|||
|
||||
func (*Log) SetCommand(*cmd.Interpreter) {}
|
||||
func (*Log) SetFilter(string) {}
|
||||
func (*Log) SetLabelFilter(map[string]string) {}
|
||||
func (*Log) SetLabelSelector(labels.Selector) {}
|
||||
|
||||
// Init initializes the viewer.
|
||||
func (l *Log) Init(ctx context.Context) (err error) {
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ func (n *Node) bindKeys(aa *ui.KeyActions) {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/view/cmd"
|
||||
"github.com/derailed/tcell/v2"
|
||||
"github.com/derailed/tview"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
// Picker represents a container picker.
|
||||
|
|
@ -30,7 +31,7 @@ func NewPicker() *Picker {
|
|||
|
||||
func (*Picker) SetCommand(*cmd.Interpreter) {}
|
||||
func (*Picker) SetFilter(string) {}
|
||||
func (*Picker) SetLabelFilter(map[string]string) {}
|
||||
func (*Picker) SetLabelSelector(labels.Selector) {}
|
||||
|
||||
// Init initializes the view.
|
||||
func (p *Picker) Init(ctx context.Context) error {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/derailed/tview"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
// Graphable represents a graphic component.
|
||||
|
|
@ -80,7 +81,7 @@ func NewPulse(gvr *client.GVR) ResourceViewer {
|
|||
|
||||
func (*Pulse) SetCommand(*cmd.Interpreter) {}
|
||||
func (*Pulse) SetFilter(string) {}
|
||||
func (*Pulse) SetLabelFilter(map[string]string) {}
|
||||
func (*Pulse) SetLabelSelector(labels.Selector) {}
|
||||
|
||||
// Init initializes the view.
|
||||
func (p *Pulse) Init(ctx context.Context) error {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/k9s/internal/ui/dialog"
|
||||
"github.com/derailed/tcell/v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// 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())
|
||||
}
|
||||
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())
|
||||
defer cancel()
|
||||
for _, path := range paths {
|
||||
if err := r.restartRollout(ctx, path); err != nil {
|
||||
r.App().Flash().Err(err)
|
||||
} else {
|
||||
r.App().Flash().Infof("Restart in progress for `%s...", path)
|
||||
|
||||
opts := dialog.RestartDialogOpts{
|
||||
Title: "Confirm Restart",
|
||||
Message: msg,
|
||||
FieldManager: "kubectl-rollout",
|
||||
Ack: func(opts *metav1.PatchOptions) bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), r.App().Conn().Config().CallTimeout())
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, func() {})
|
||||
return true
|
||||
},
|
||||
Cancel: func() {},
|
||||
}
|
||||
dialog.ShowRestart(&d, r.App().Content.Pages, &opts)
|
||||
|
||||
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())
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -79,7 +89,7 @@ func (r *RestartExtender) restartRollout(ctx context.Context, path string) error
|
|||
return errors.New("resource is not restartable")
|
||||
}
|
||||
|
||||
return s.Restart(ctx, path)
|
||||
return s.Restart(ctx, path, opts)
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ func (s *Service) showPods(a *App, _ ui.Tabular, _ *client.GVR, path string) {
|
|||
return
|
||||
}
|
||||
|
||||
showPods(a, path, toLabelsStr(svc.Spec.Selector), "")
|
||||
showPods(a, path, labels.SelectorFromSet(svc.Spec.Selector), "")
|
||||
}
|
||||
|
||||
func (*Service) checkSvc(svc *v1.Service) error {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
@ -135,8 +136,8 @@ var _ ui.Tabular = (*mockTableModel)(nil)
|
|||
|
||||
func (*mockTableModel) SetViewSetting(context.Context, *config.ViewSetting) {}
|
||||
func (*mockTableModel) SetInstance(string) {}
|
||||
func (*mockTableModel) SetLabelFilter(string) {}
|
||||
func (*mockTableModel) GetLabelFilter() string { return "" }
|
||||
func (*mockTableModel) SetLabelSelector(labels.Selector) {}
|
||||
func (*mockTableModel) GetLabelSelector() labels.Selector { return nil }
|
||||
func (*mockTableModel) Empty() bool { return false }
|
||||
func (*mockTableModel) RowCount() int { return 1 }
|
||||
func (*mockTableModel) HasMetrics() bool { return true }
|
||||
|
|
|
|||
|
|
@ -112,7 +112,9 @@ 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()) {
|
||||
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.KeyWithMetrics, w.App().factory.Client().HasMetrics())
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
|
|
@ -58,7 +59,7 @@ func NewXray(gvr *client.GVR) ResourceViewer {
|
|||
|
||||
func (*Xray) SetCommand(*cmd.Interpreter) {}
|
||||
func (*Xray) SetFilter(string) {}
|
||||
func (*Xray) SetLabelFilter(map[string]string) {}
|
||||
func (*Xray) SetLabelSelector(labels.Selector) {}
|
||||
|
||||
// Init initializes the view.
|
||||
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(ctx, internal.KeyFields, "")
|
||||
if x.CmdBuff().Empty() {
|
||||
ctx = context.WithValue(ctx, internal.KeyLabels, "")
|
||||
ctx = context.WithValue(ctx, internal.KeyLabels, labels.Everything())
|
||||
} 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
|
||||
|
|
@ -688,7 +691,9 @@ func (x *Xray) styleTitle() string {
|
|||
return title
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -14,18 +14,18 @@ import (
|
|||
"github.com/anchore/clio"
|
||||
"github.com/anchore/grype/cmd/grype/cli/options"
|
||||
"github.com/anchore/grype/grype"
|
||||
"github.com/anchore/grype/grype/db/legacy/distribution"
|
||||
v5 "github.com/anchore/grype/grype/db/v5"
|
||||
"github.com/anchore/grype/grype/db/v5/matcher"
|
||||
"github.com/anchore/grype/grype/db/v5/matcher/dotnet"
|
||||
"github.com/anchore/grype/grype/db/v5/matcher/golang"
|
||||
"github.com/anchore/grype/grype/db/v5/matcher/java"
|
||||
"github.com/anchore/grype/grype/db/v5/matcher/javascript"
|
||||
"github.com/anchore/grype/grype/db/v5/matcher/python"
|
||||
"github.com/anchore/grype/grype/db/v5/matcher/ruby"
|
||||
"github.com/anchore/grype/grype/db/v5/matcher/stock"
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/matcher"
|
||||
"github.com/anchore/grype/grype/matcher/dotnet"
|
||||
"github.com/anchore/grype/grype/matcher/golang"
|
||||
"github.com/anchore/grype/grype/matcher/java"
|
||||
"github.com/anchore/grype/grype/matcher/javascript"
|
||||
"github.com/anchore/grype/grype/matcher/python"
|
||||
"github.com/anchore/grype/grype/matcher/ruby"
|
||||
"github.com/anchore/grype/grype/matcher/stock"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/vex"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/slogs"
|
||||
|
|
@ -40,8 +40,8 @@ const (
|
|||
)
|
||||
|
||||
type imageScanner struct {
|
||||
store *v5.ProviderStore
|
||||
dbStatus *distribution.Status
|
||||
provider vulnerability.Provider
|
||||
status *vulnerability.ProviderStatus
|
||||
opts *options.Grype
|
||||
scans Scans
|
||||
mx sync.RWMutex
|
||||
|
|
@ -90,8 +90,9 @@ func (s *imageScanner) Init(name, version string) {
|
|||
s.opts.GenerateMissingCPEs = true
|
||||
|
||||
var err error
|
||||
s.store, s.dbStatus, err = grype.LoadVulnerabilityDB(
|
||||
s.opts.DB.ToLegacyCuratorConfig(),
|
||||
s.provider, s.status, err = grype.LoadVulnerabilityDB(
|
||||
s.opts.ToClientConfig(),
|
||||
s.opts.ToCuratorConfig(),
|
||||
s.opts.DB.AutoUpdate,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -99,7 +100,7 @@ func (s *imageScanner) Init(name, version string) {
|
|||
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)
|
||||
return
|
||||
}
|
||||
|
|
@ -112,9 +113,9 @@ func (s *imageScanner) Stop() {
|
|||
s.mx.RLock()
|
||||
defer s.mx.RUnlock()
|
||||
|
||||
if s.store != nil {
|
||||
_ = s.store.Close()
|
||||
s.store = nil
|
||||
if s.provider != nil {
|
||||
_ = s.provider.Close()
|
||||
s.provider = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,11 +181,11 @@ func (s *imageScanner) scan(_ context.Context, img string, sc *Scan) error {
|
|||
}
|
||||
|
||||
v := grype.VulnerabilityMatcher{
|
||||
Store: *s.store,
|
||||
IgnoreRules: s.opts.Ignore,
|
||||
NormalizeByCVE: s.opts.ByCVE,
|
||||
FailSeverity: s.opts.FailOnSeverity(),
|
||||
Matchers: getMatchers(s.opts),
|
||||
VulnerabilityProvider: s.provider,
|
||||
IgnoreRules: s.opts.Ignore,
|
||||
NormalizeByCVE: s.opts.ByCVE,
|
||||
FailSeverity: s.opts.FailOnSeverity(),
|
||||
Matchers: getMatchers(s.opts),
|
||||
VexProcessor: vex.NewProcessor(vex.ProcessorOptions{
|
||||
Documents: s.opts.VexDocuments,
|
||||
IgnoreRules: s.opts.Ignore,
|
||||
|
|
@ -195,7 +196,7 @@ func (s *imageScanner) scan(_ context.Context, img string, sc *Scan) error {
|
|||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -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(
|
||||
matcher.Config{
|
||||
Java: java.MatcherConfig{
|
||||
|
|
@ -230,23 +231,23 @@ func getMatchers(opts *options.Grype) []matcher.Matcher {
|
|||
Dotnet: dotnet.MatcherConfig(opts.Match.Dotnet),
|
||||
Javascript: javascript.MatcherConfig(opts.Match.Javascript),
|
||||
Golang: golang.MatcherConfig{
|
||||
UseCPEs: opts.Match.Golang.UseCPEs,
|
||||
AlwaysUseCPEForStdlib: opts.Match.Golang.AlwaysUseCPEForStdlib,
|
||||
UseCPEs: opts.Match.Golang.UseCPEs,
|
||||
AlwaysUseCPEForStdlib: opts.Match.Golang.AlwaysUseCPEForStdlib,
|
||||
AllowMainModulePseudoVersionComparison: opts.Match.Golang.AllowMainModulePseudoVersionComparison,
|
||||
},
|
||||
Stock: stock.MatcherConfig(opts.Match.Stock),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func validateDBLoad(loadErr error, status *distribution.Status) error {
|
||||
func validateDBLoad(loadErr error, status *vulnerability.ProviderStatus) error {
|
||||
if loadErr != nil {
|
||||
return fmt.Errorf("failed to load vulnerability db: %w", loadErr)
|
||||
}
|
||||
if status == nil {
|
||||
return fmt.Errorf("unable to determine the status of the vulnerability db")
|
||||
}
|
||||
if status.Err != nil {
|
||||
return fmt.Errorf("db could not be loaded: %w", status.Err)
|
||||
if status.Error != nil {
|
||||
return fmt.Errorf("db could not be loaded: %w", status.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name: k9s
|
||||
base: core22
|
||||
version: 'v0.50.3'
|
||||
version: 'v0.50.4'
|
||||
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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue